Browse Source

apply prettier to everything.

Login
fiatjaf 5 years ago
parent
commit
4730500ed7
  1. 2
      .gitignore
  2. 12
      .prettierrc
  3. 2
      Makefile
  4. 2
      lnbits/core/static/js/extensions.js
  5. 8
      lnbits/core/static/js/index.js
  6. 305
      lnbits/core/static/js/wallet.js
  7. 20
      lnbits/extensions/amilk/templates/amilk/_api_docs.html
  8. 208
      lnbits/extensions/amilk/templates/amilk/index.html
  9. 105
      lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html
  10. 814
      lnbits/extensions/diagonalley/templates/diagonalley/index.html
  11. 4
      lnbits/extensions/diagonalley/templates/diagonalley/stall.html
  12. 219
      lnbits/extensions/events/templates/events/display.html
  13. 147
      lnbits/extensions/events/templates/events/index.html
  14. 184
      lnbits/extensions/events/templates/events/registration.html
  15. 159
      lnbits/extensions/events/templates/events/ticket.html
  16. 39
      lnbits/extensions/example/templates/example/index.html
  17. 20
      lnbits/extensions/paywall/templates/paywall/_api_docs.html
  18. 95
      lnbits/extensions/paywall/templates/paywall/display.html
  19. 212
      lnbits/extensions/paywall/templates/paywall/index.html
  20. 61
      lnbits/extensions/tpos/templates/tpos/_api_docs.html
  21. 19
      lnbits/extensions/tpos/templates/tpos/_tpos.html
  22. 372
      lnbits/extensions/tpos/templates/tpos/index.html
  23. 244
      lnbits/extensions/tpos/templates/tpos/tpos.html
  24. 172
      lnbits/extensions/usermanager/templates/usermanager/_api_docs.html
  25. 377
      lnbits/extensions/usermanager/templates/usermanager/index.html
  26. 98
      lnbits/extensions/withdraw/templates/withdraw/_api_docs.html
  27. 31
      lnbits/extensions/withdraw/templates/withdraw/_lnurl.html
  28. 31
      lnbits/extensions/withdraw/templates/withdraw/display.html
  29. 220
      lnbits/extensions/withdraw/templates/withdraw/index.html
  30. 29
      lnbits/extensions/withdraw/templates/withdraw/print_qr.html
  31. 230
      lnbits/static/js/base.js
  32. 63
      lnbits/static/js/components.js
  33. 5
      package.json

2
.gitignore

@ -27,3 +27,5 @@ venv
.pyre*
__bundle__
node_modules

12
.prettierrc

@ -0,0 +1,12 @@
{
"semi": false,
"arrowParens": "avoid",
"insertPragma": false,
"printWidth": 80,
"proseWrap": "preserve",
"singleQuote": true,
"trailingComma": "none",
"useTabs": false,
"jsxBracketSameLine": false,
"bracketSpacing": false
}

2
Makefile

@ -0,0 +1,2 @@
prettier:
./node_modules/.bin/prettier --write lnbits/static/js/** lnbits/core/static/js/** lnbits/extensions/*/templates/**

2
lnbits/core/static/js/extensions.js

@ -1,4 +1,4 @@
new Vue({
el: '#vue',
mixins: [windowMixin]
});
})

8
lnbits/core/static/js/index.js

@ -8,18 +8,18 @@ new Vue({
data: {}
},
walletName: ''
};
}
},
methods: {
createWallet: function () {
LNbits.href.createWallet(this.walletName);
LNbits.href.createWallet(this.walletName)
},
processing: function () {
this.$q.notify({
timeout: 0,
message: 'Processing...',
icon: null
});
})
}
}
});
})

305
lnbits/core/static/js/wallet.js

@ -1,39 +1,49 @@
Vue.component(VueQrcode.name, VueQrcode);
Vue.use(VueQrcodeReader);
Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader)
function generateChart(canvas, payments) {
var txs = [];
var n = 0;
var txs = []
var n = 0
var data = {
labels: [],
income: [],
outcome: [],
cumulative: []
};
}
_.each(payments.slice(0).sort(function (a, b) {
return a.time - b.time;
}), function (tx) {
_.each(
payments.slice(0).sort(function (a, b) {
return a.time - b.time
}),
function (tx) {
txs.push({
hour: Quasar.utils.date.formatDate(tx.date, 'YYYY-MM-DDTHH:00'),
sat: tx.sat,
});
});
sat: tx.sat
})
}
)
_.each(_.groupBy(txs, 'hour'), function (value, day) {
var income = _.reduce(value, function(memo, tx) {
return (tx.sat >= 0) ? memo + tx.sat : memo;
}, 0);
var outcome = _.reduce(value, function(memo, tx) {
return (tx.sat < 0) ? memo + Math.abs(tx.sat) : memo;
}, 0);
n = n + income - outcome;
data.labels.push(day);
data.income.push(income);
data.outcome.push(outcome);
data.cumulative.push(n);
});
var income = _.reduce(
value,
function (memo, tx) {
return tx.sat >= 0 ? memo + tx.sat : memo
},
0
)
var outcome = _.reduce(
value,
function (memo, tx) {
return tx.sat < 0 ? memo + Math.abs(tx.sat) : memo
},
0
)
n = n + income - outcome
data.labels.push(day)
data.income.push(income)
data.outcome.push(outcome)
data.cumulative.push(n)
})
new Chart(canvas.getContext('2d'), {
type: 'bar',
@ -75,7 +85,8 @@ function generateChart(canvas, payments) {
intersect: false
},
scales: {
xAxes: [{
xAxes: [
{
type: 'time',
display: true,
offset: true,
@ -83,7 +94,8 @@ function generateChart(canvas, payments) {
minUnit: 'hour',
stepSize: 3
}
}],
}
]
},
// performance tweaks
animation: {
@ -95,10 +107,9 @@ function generateChart(canvas, payments) {
}
}
}
});
})
}
new Vue({
el: '#vue',
mixins: [windowMixin],
@ -128,8 +139,20 @@ new Vue({
paymentsTable: {
columns: [
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'},
{name: 'date', align: 'left', label: 'Date', field: 'date', sortable: true},
{name: 'sat', align: 'right', label: 'Amount (sat)', field: 'sat', sortable: true}
{
name: 'date',
align: 'left',
label: 'Date',
field: 'date',
sortable: true
},
{
name: 'sat',
align: 'right',
label: 'Amount (sat)',
field: 'sat',
sortable: true
}
],
pagination: {
rowsPerPage: 10
@ -143,46 +166,50 @@ new Vue({
show: false,
location: window.location
}
};
}
},
computed: {
filteredPayments: function () {
var q = this.paymentsTable.filter;
if (!q || q == '') return this.payments;
var q = this.paymentsTable.filter
if (!q || q == '') return this.payments
return LNbits.utils.search(this.payments, q);
return LNbits.utils.search(this.payments, q)
},
balance: function () {
if (this.payments.length) {
return _.pluck(this.payments, 'amount').reduce(function (a, b) { return a + b; }, 0) / 1000;
return (
_.pluck(this.payments, 'amount').reduce(function (a, b) {
return a + b
}, 0) / 1000
)
}
return this.g.wallet.sat;
return this.g.wallet.sat
},
fbalance: function () {
return LNbits.utils.formatSat(this.balance)
},
canPay: function () {
if (!this.send.invoice) return false;
return this.send.invoice.sat <= this.balance;
if (!this.send.invoice) return false
return this.send.invoice.sat <= this.balance
},
pendingPaymentsExist: function () {
return (this.payments)
return this.payments
? _.where(this.payments, {pending: 1}).length > 0
: false;
: false
}
},
methods: {
closeCamera: function () {
this.sendCamera.show = false;
this.sendCamera.show = false
},
showCamera: function () {
this.sendCamera.show = true;
this.sendCamera.show = true
},
showChart: function () {
this.paymentsChart.show = true;
this.paymentsChart.show = true
this.$nextTick(function () {
generateChart(this.$refs.canvas, this.payments);
});
generateChart(this.$refs.canvas, this.payments)
})
},
showReceiveDialog: function () {
this.receive = {
@ -194,7 +221,7 @@ new Vue({
memo: ''
},
paymentChecker: null
};
}
},
showSendDialog: function () {
this.send = {
@ -204,57 +231,64 @@ new Vue({
bolt11: ''
},
paymentChecker: null
};
}
},
closeReceiveDialog: function () {
var checker = this.receive.paymentChecker;
var checker = this.receive.paymentChecker
setTimeout(function () {
clearInterval(checker);
}, 10000);
clearInterval(checker)
}, 10000)
},
closeSendDialog: function () {
this.sendCamera.show = false;
var checker = this.send.paymentChecker;
this.sendCamera.show = false
var checker = this.send.paymentChecker
setTimeout(function () {
clearInterval(checker);
}, 1000);
clearInterval(checker)
}, 1000)
},
createInvoice: function () {
var self = this;
this.receive.status = 'loading';
LNbits.api.createInvoice(this.g.wallet, this.receive.data.amount, this.receive.data.memo)
var self = this
this.receive.status = 'loading'
LNbits.api
.createInvoice(
this.g.wallet,
this.receive.data.amount,
this.receive.data.memo
)
.then(function (response) {
self.receive.status = 'success';
self.receive.paymentReq = response.data.payment_request;
self.receive.status = 'success'
self.receive.paymentReq = response.data.payment_request
self.receive.paymentChecker = setInterval(function () {
LNbits.api.getPayment(self.g.wallet, response.data.checking_id).then(function (response) {
LNbits.api
.getPayment(self.g.wallet, response.data.checking_id)
.then(function (response) {
if (response.data.paid) {
self.fetchPayments();
self.receive.show = false;
clearInterval(self.receive.paymentChecker);
self.fetchPayments()
self.receive.show = false
clearInterval(self.receive.paymentChecker)
}
});
}, 2000);
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
self.receive.status = 'pending';
});
})
}, 2000)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
self.receive.status = 'pending'
})
},
decodeQR: function (res) {
this.send.data.bolt11 = res;
this.decodeInvoice();
this.sendCamera.show = false;
this.send.data.bolt11 = res
this.decodeInvoice()
this.sendCamera.show = false
},
decodeInvoice: function () {
if (this.send.data.bolt11.startsWith('lightning:')) {
this.send.data.bolt11 = this.send.data.bolt11.slice(10);
this.send.data.bolt11 = this.send.data.bolt11.slice(10)
}
let invoice;
let invoice
try {
invoice = decode(this.send.data.bolt11);
invoice = decode(this.send.data.bolt11)
} catch (error) {
this.$q.notify({
timeout: 3000,
@ -262,101 +296,120 @@ new Vue({
message: error + '.',
caption: '400 BAD REQUEST',
icon: null
});
return;
})
return
}
let cleanInvoice = {
msat: invoice.human_readable_part.amount,
sat: invoice.human_readable_part.amount / 1000,
fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000)
};
}
_.each(invoice.data.tags, function (tag) {
if (_.isObject(tag) && _.has(tag, 'description')) {
if (tag.description == 'payment_hash') { cleanInvoice.hash = tag.value; }
else if (tag.description == 'description') { cleanInvoice.description = tag.value; }
else if (tag.description == 'expiry') {
var expireDate = new Date((invoice.data.time_stamp + tag.value) * 1000);
cleanInvoice.expireDate = Quasar.utils.date.formatDate(expireDate, 'YYYY-MM-DDTHH:mm:ss.SSSZ');
cleanInvoice.expired = false; // TODO
if (tag.description == 'payment_hash') {
cleanInvoice.hash = tag.value
} else if (tag.description == 'description') {
cleanInvoice.description = tag.value
} else if (tag.description == 'expiry') {
var expireDate = new Date(
(invoice.data.time_stamp + tag.value) * 1000
)
cleanInvoice.expireDate = Quasar.utils.date.formatDate(
expireDate,
'YYYY-MM-DDTHH:mm:ss.SSSZ'
)
cleanInvoice.expired = false // TODO
}
}
});
})
this.send.invoice = Object.freeze(cleanInvoice);
this.send.invoice = Object.freeze(cleanInvoice)
},
payInvoice: function () {
var self = this;
var self = this
dismissPaymentMsg = this.$q.notify({
timeout: 0,
message: 'Processing payment...',
icon: null
});
})
LNbits.api.payInvoice(this.g.wallet, this.send.data.bolt11).then(function (response) {
LNbits.api
.payInvoice(this.g.wallet, this.send.data.bolt11)
.then(function (response) {
self.send.paymentChecker = setInterval(function () {
LNbits.api.getPayment(self.g.wallet, response.data.checking_id).then(function (res) {
LNbits.api
.getPayment(self.g.wallet, response.data.checking_id)
.then(function (res) {
if (res.data.paid) {
self.send.show = false;
clearInterval(self.send.paymentChecker);
dismissPaymentMsg();
self.fetchPayments();
self.send.show = false
clearInterval(self.send.paymentChecker)
dismissPaymentMsg()
self.fetchPayments()
}
});
}, 2000);
}).catch(function (error) {
dismissPaymentMsg();
LNbits.utils.notifyApiError(error);
});
})
}, 2000)
})
.catch(function (error) {
dismissPaymentMsg()
LNbits.utils.notifyApiError(error)
})
},
deleteWallet: function (walletId, user) {
LNbits.utils.confirmDialog(
'Are you sure you want to delete this wallet?'
).onOk(function () {
LNbits.href.deleteWallet(walletId, user);
});
LNbits.utils
.confirmDialog('Are you sure you want to delete this wallet?')
.onOk(function () {
LNbits.href.deleteWallet(walletId, user)
})
},
fetchPayments: function (checkPending) {
var self = this;
var self = this
return LNbits.api.getPayments(this.g.wallet, checkPending).then(function (response) {
self.payments = response.data.map(function (obj) {
return LNbits.map.payment(obj);
}).sort(function (a, b) {
return b.time - a.time;
});
});
return LNbits.api
.getPayments(this.g.wallet, checkPending)
.then(function (response) {
self.payments = response.data
.map(function (obj) {
return LNbits.map.payment(obj)
})
.sort(function (a, b) {
return b.time - a.time
})
})
},
checkPendingPayments: function () {
var dismissMsg = this.$q.notify({
timeout: 0,
message: 'Checking pending transactions...',
icon: null
});
})
this.fetchPayments(true).then(function () {
dismissMsg();
});
dismissMsg()
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paymentsTable.columns, this.payments);
LNbits.utils.exportCSV(this.paymentsTable.columns, this.payments)
}
},
watch: {
'payments': function () {
EventHub.$emit('update-wallet-balance', [this.g.wallet.id, this.balance]);
payments: function () {
EventHub.$emit('update-wallet-balance', [this.g.wallet.id, this.balance])
}
},
created: function () {
this.fetchPayments();
setTimeout(this.checkPendingPayments(), 1200);
this.fetchPayments()
setTimeout(this.checkPendingPayments(), 1200)
},
mounted: function () {
if (this.$refs.disclaimer && !this.$q.localStorage.getItem('lnbits.disclaimerShown')) {
this.disclaimerDialog.show = true;
this.$q.localStorage.set('lnbits.disclaimerShown', true);
if (
this.$refs.disclaimer &&
!this.$q.localStorage.getItem('lnbits.disclaimerShown')
) {
this.disclaimerDialog.show = true
this.$q.localStorage.set('lnbits.disclaimerShown', true)
}
}
});
})

20
lnbits/extensions/amilk/templates/amilk/_api_docs.html

@ -1,4 +1,3 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
@ -8,9 +7,18 @@
<q-card>
<q-card-section>
<h5 class="text-subtitle1 q-my-none">Assistant Faucet Milker</h5>
<p>Milking faucets with software, known as "assmilking", seems at first to be black-hat, although in fact there might be some unexplored use cases. An LNURL withdraw gives someone the right to pull funds, which can be done over time. An LNURL withdraw could be used outside of just faucets, to provide money streaming and repeat payments.<br/>Paste or scan an LNURL withdraw, enter the amount for the AMilk to pull and the frequency for it to be pulled.<br/>
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p>
</q-card>
<p>
Milking faucets with software, known as "assmilking", seems at first to
be black-hat, although in fact there might be some unexplored use cases.
An LNURL withdraw gives someone the right to pull funds, which can be
done over time. An LNURL withdraw could be used outside of just faucets,
to provide money streaming and repeat payments.<br />Paste or scan an
LNURL withdraw, enter the amount for the AMilk to pull and the frequency
for it to be pulled.<br />
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
>
</p>
</q-card-section>
</q-card-section></q-expansion-item>
</q-card>
</q-expansion-item>

208
lnbits/extensions/amilk/templates/amilk/index.html

@ -1,14 +1,12 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block page %}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="amilkDialog.show = true">New AMilk</q-btn>
<q-btn unelevated color="deep-purple" @click="amilkDialog.show = true"
>New AMilk</q-btn
>
</q-card-section>
</q-card>
@ -22,38 +20,36 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="amilks"
row-key="id"
:columns="amilksTable.columns"
:pagination.sync="amilksTable.pagination">
:pagination.sync="amilksTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteAMilk(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteAMilk(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -66,7 +62,9 @@
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">LNbits Assistant Faucet Milker Extension</h6>
<h6 class="text-subtitle1 q-my-none">
LNbits Assistant Faucet Milker Extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@ -80,40 +78,59 @@
<q-dialog v-model="amilkDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="createAMilk" class="q-gutter-md">
<q-select filled dense emit-value v-model="amilkDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
<q-select
filled
dense
emit-value
v-model="amilkDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input filled dense
<q-input
filled
dense
v-model.trim="amilkDialog.data.lnurl"
type="url"
label="LNURL Withdraw"></q-input>
<q-input filled dense
label="LNURL Withdraw"
></q-input>
<q-input
filled
dense
v-model.number="amilkDialog.data.amount"
type="number"
label="Amount *"></q-input>
<q-input filled dense
label="Amount *"
></q-input>
<q-input
filled
dense
v-model.trim="amilkDialog.data.atime"
type="number"
label="Hit frequency (secs)"
placeholder="Frequency to be hit"></q-input>
<q-btn unelevated
placeholder="Frequency to be hit"
></q-input>
<q-btn
unelevated
color="deep-purple"
:disable="amilkDialog.data.amount == null || amilkDialog.data.amount < 0 || amilkDialog.data.lnurl == null"
type="submit">Create amilk</q-btn>
type="submit"
>Create amilk</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %}
{% block scripts %}
{{ window_vars(user) }}
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapAMilk = function (obj) {
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
obj.wall = ['/amilk/', obj.id].join('');
return obj;
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.wall = ['/amilk/', obj.id].join('')
return obj
}
new Vue({
@ -137,94 +154,99 @@
show: false,
data: {}
}
};
}
},
methods: {
getAMilks: function () {
var self = this;
var self = this
LNbits.api.request(
LNbits.api
.request(
'GET',
'/amilk/api/v1/amilk?all_wallets',
this.g.user.wallets[0].inkey
).then(function (response) {
)
.then(function (response) {
self.amilks = response.data.map(function (obj) {
response.data.forEach(MILK);
response.data.forEach(MILK)
function MILK(item) {
window.setInterval(function () {
LNbits.api.request(
LNbits.api
.request(
'GET',
'/amilk/api/v1/amilk/milk/' + item.id,
"Lorem"
).then(function (response) {
'Lorem'
)
.then(function (response) {
self.amilks = response.data.map(function (obj) {
return mapAMilk(obj);
});
});
},
item.atime*1000);
return mapAMilk(obj)
})
})
}, item.atime * 1000)
}
return mapAMilk(obj);
});
});
return mapAMilk(obj)
})
})
},
createAMilk: function () {
var data = {
lnurl: this.amilkDialog.data.lnurl,
atime: parseInt(this.amilkDialog.data.atime),
amount: this.amilkDialog.data.amount
};
var self = this;
}
var self = this
console.log(this.amilkDialog.data.wallet);
console.log(this.amilkDialog.data.wallet)
LNbits.api.request(
LNbits.api
.request(
'POST',
'/amilk/api/v1/amilk',
_.findWhere(this.g.user.wallets, {id: this.amilkDialog.data.wallet}).inkey,
_.findWhere(this.g.user.wallets, {id: this.amilkDialog.data.wallet})
.inkey,
data
).then(function (response) {
self.amilks.push(mapAMilk(response.data));
self.amilkDialog.show = false;
self.amilkDialog.data = {};
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
)
.then(function (response) {
self.amilks.push(mapAMilk(response.data))
self.amilkDialog.show = false
self.amilkDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteAMilk: function (amilkId) {
var self = this;
var amilk = _.findWhere(this.amilks, {id: amilkId});
LNbits.utils.confirmDialog(
'Are you sure you want to delete this AMilk link?'
).onOk(function () {
LNbits.api.request(
var self = this
var amilk = _.findWhere(this.amilks, {id: amilkId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this AMilk link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/amilk/api/v1/amilks/' + amilkId,
_.findWhere(self.g.user.wallets, {id: amilk.wallet}).inkey
).then(function (response) {
self.amilks = _.reject(self.amilks, function (obj) { return obj.id == amilkId; });
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
});
)
.then(function (response) {
self.amilks = _.reject(self.amilks, function (obj) {
return obj.id == amilkId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.amilksTable.columns, this.amilks);
LNbits.utils.exportCSV(this.amilksTable.columns, this.amilks)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getAMilks();
this.getAMilks()
}
}
});
})
</script>
{% endblock %}

105
lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html

@ -1,4 +1,3 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
@ -7,14 +6,24 @@
>
<q-card>
<q-card-section>
<h5 class="text-subtitle1 q-my-none">Diagon Alley: Decentralised Market-Stalls</h5>
<p>Make a list of products to sell, point your list of products at a public indexer. Buyers browse your products on the indexer, and pay you directly. Ratings are managed by the indexer. Your stall can be listed in multiple indexers, even over TOR, if you wish to be anonymous.<br/>
More information on the <a href="https://github.com/lnbits/Diagon-Alley">Diagon Alley Protocol</a><br/>
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p>
</q-card>
</q-card-section>
<h5 class="text-subtitle1 q-my-none">
Diagon Alley: Decentralised Market-Stalls
</h5>
<p>
Make a list of products to sell, point your list of products at a public
indexer. Buyers browse your products on the indexer, and pay you
directly. Ratings are managed by the indexer. Your stall can be listed
in multiple indexers, even over TOR, if you wish to be anonymous.<br />
More information on the
<a href="https://github.com/lnbits/Diagon-Alley"
>Diagon Alley Protocol</a
><br />
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
>
</p>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="extras"
@ -22,43 +31,91 @@
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="Get prodcuts, categorised by wallet">
<q-expansion-item
group="api"
dense
expand-separator
label="Get prodcuts, categorised by wallet"
>
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /api/v1/diagonalley/stall/products/&lt;indexer_id&gt;</code>
<code
><span class="text-light-blue">GET</span>
/api/v1/diagonalley/stall/products/&lt;indexer_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>Product JSON list</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}diagonalley/api/v1/diagonalley/stall/products/&lt;indexer_id&gt;</code>
<code
>curl -X GET {{ request.url_root
}}diagonalley/api/v1/diagonalley/stall/products/&lt;indexer_id&gt;</code
>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Get invoice for product">
<q-expansion-item
group="api"
dense
expand-separator
label="Get invoice for product"
>
<q-card>
<q-card-section>
<code><span class="text-light-green">POST</span> /api/v1/diagonalley/stall/order/&lt;indexer_id&gt;</code>
<code
><span class="text-light-green">POST</span>
/api/v1/diagonalley/stall/order/&lt;indexer_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"id": &lt;string&gt;, "address": &lt;string&gt;, "shippingzone": &lt;integer&gt;, "email": &lt;string&gt;, "quantity": &lt;integer&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"checking_id": &lt;string&gt;,"payment_request": &lt;string&gt;}</code>
<code
>{"id": &lt;string&gt;, "address": &lt;string&gt;, "shippingzone":
&lt;integer&gt;, "email": &lt;string&gt;, "quantity":
&lt;integer&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"checking_id": &lt;string&gt;,"payment_request":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}diagonalley/api/v1/diagonalley/stall/order/&lt;indexer_id&gt; -d '{"id": &lt;product_id&&gt;, "email": &lt;customer_email&gt;, "address": &lt;customer_address&gt;, "quantity": 2, "shippingzone": 1}' -H "Content-type: application/json"
<code
>curl -X POST {{ request.url_root
}}diagonalley/api/v1/diagonalley/stall/order/&lt;indexer_id&gt; -d
'{"id": &lt;product_id&&gt;, "email": &lt;customer_email&gt;,
"address": &lt;customer_address&gt;, "quantity": 2, "shippingzone":
1}' -H "Content-type: application/json"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Check a product has been shipped"
class="q-mb-md">
<q-expansion-item
group="api"
dense
expand-separator
label="Check a product has been shipped"
class="q-mb-md"
>
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /diagonalley/api/v1/diagonalley/stall/checkshipped/&lt;checking_id&gt;</code>
<code
><span class="text-light-blue">GET</span>
/diagonalley/api/v1/diagonalley/stall/checkshipped/&lt;checking_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 200 OK (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>{"shipped": &lt;boolean&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}diagonalley/api/v1/diagonalley/stall/checkshipped/&lt;checking_id&gt; -H "Content-type: application/json"</code>
<code
>curl -X GET {{ request.url_root
}}diagonalley/api/v1/diagonalley/stall/checkshipped/&lt;checking_id&gt;
-H "Content-type: application/json"</code
>
</q-card-section>
</q-card>
</q-expansion-item>

814
lnbits/extensions/diagonalley/templates/diagonalley/index.html

File diff suppressed because it is too large

4
lnbits/extensions/diagonalley/templates/diagonalley/stall.html

@ -1 +1,3 @@
<script>console.log("{{ stall }}")</script>
<script>
console.log('{{ stall }}')
</script>

219
lnbits/extensions/events/templates/events/display.html

@ -202,15 +202,11 @@
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background:
#1f2234;
background: #1f2234;
}
body {
@ -224,60 +220,50 @@ body {
}
.skin-blue .main-header .navbar {
background-color:
#2e507d;
background-color: #2e507d;
}
.content-wrapper, .right-side {
background-color:
#1f2234;
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color:
#1f2234;
color:
#fff;
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color:
#4b646f;
background:
#1f2234;
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
background:
#1f2234;
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background:
#1f2234;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid
transparent;
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background:
#3e355a;
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color:
#3e355a;
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
@ -290,28 +276,36 @@ background:
background-color: #1f2234;
}
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
background-color:
#1f2234 !important;
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger, .alert-error {
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid
#fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus {
color:
#f6f6f6;
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
background-color:
#3e355a !important;
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
@ -321,19 +315,15 @@ background:
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color:
#333646;
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
@ -348,7 +338,9 @@ color: #8964a9;
border-bottom: none;
}
a:hover, a:active, a:focus {
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
@ -358,48 +350,35 @@ a:hover, a:active, a:focus {
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color:
#333646;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color:
#333646;
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color:
#3e355a;
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
@ -421,7 +400,6 @@ background-color:
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
@ -431,9 +409,7 @@ background-color:
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;">
</section>
<section class="sidebar" style="height: auto;"></section>
<!-- /.sidebar -->
</aside>
@ -445,40 +421,62 @@ background-color:
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content"><br/><br/>
<center><h1 style="font-size:500%">{{ nme }}</h1></center>
<center><h2 style="width:55%;word-wrap: break-word;" >{{ descr }}</h2></center>
<section class="content">
<br /><br />
<center><h1 style="font-size: 500%;">{{ nme }}</h1></center>
<center>
<h2 style="width: 55%; word-wrap: break-word;">{{ descr }}</h2>
</center>
<div id="theform">
<br /><br /><br />
<center>
<form role="form">
<div class="form-group" style="width: 300px;">
<input id="Nam" type="text" class="form-control" placeholder="Name"></input>
<input id="Ema" type="text" class="form-control" placeholder="Email"></input>
<input
id="Nam"
type="text"
class="form-control"
placeholder="Name"
/>
<input
id="Ema"
type="text"
class="form-control"
placeholder="Email"
/>
</div>
<button onclick="submitforticket()" type="button" class="btn btn-info">Go to payment</button><p style="color:red;" id="error"></p>
<button
onclick="submitforticket()"
type="button"
class="btn btn-info"
>
Go to payment
</button>
<p style="color: red;" id="error"></p>
</form>
</center>
</div>
<center>
<br /><br />
<div id="qrcode" style="width: 340px;"></div>
<br /><br />
<div
style="width: 55%; word-wrap: break-word;"
id="qrcodetxt"
></div>
<br />
</center>
<center><br/><br/> <div id="qrcode" style="width: 340px;"></div><br/><br/>
<div style="width:55%;word-wrap: break-word;" id="qrcodetxt"></div> <br/></center>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</body>
<script>
function postAjax(url, data, thekey, success) {
var params =
typeof data == 'string'
@ -521,14 +519,13 @@ function postAjax(url, data, thekey, success) {
}
function submitforticket() {
nam = document.getElementById('Nam').value
ema = document.getElementById('Ema').value
postAjax(
"{{ url_for('events.api_getticket') }}?ema=" + ema,
JSON.stringify({"unireg": "{{wave }}", "name": nam}),
"filla",
JSON.stringify({unireg: '{{wave }}', name: nam}),
'filla',
function (data) {
theinvoice = JSON.parse(data).pay_req
@ -542,29 +539,29 @@ function submitforticket(){
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
})
document.getElementById('theform').innerHTML = ""
document.getElementById('theform').innerHTML = ''
document.getElementById("qrcode").style.backgroundColor = "white";
document.getElementById("qrcode").style.padding = "20px";
document.getElementById('qrcode').style.backgroundColor = 'white'
document.getElementById('qrcode').style.padding = '20px'
document.getElementById('qrcodetxt').innerHTML = theinvoice + "<br/><br/>"
document.getElementById('qrcodetxt').innerHTML =
theinvoice + '<br/><br/>'
var refreshId = setInterval(function () {
getAjax('/api/v1/invoice/' + thehash, "{{wave}}", function(datab) {
getAjax('/api/v1/invoice/' + thehash, '{{wave}}', function (datab) {
console.log(JSON.parse(datab).PAID)
if (JSON.parse(datab).PAID == 'TRUE') {
location.replace("{{ url_for('events.ticket') }}?hash="+thehash + "&unireg={{wave}}")
location.replace(
"{{ url_for('events.ticket') }}?hash=" +
thehash +
'&unireg={{wave}}'
)
clearInterval(refreshId)
}
})}, 3000);
})
}, 3000)
}
)
}
</script>
</html>

147
lnbits/extensions/events/templates/events/index.html

@ -10,9 +10,7 @@
<li class="header"><b>Instant wallet, bookmark to save</b></li>
<li></li>
</ul>
{% endblock %}
{% block menuitems %}
{% endblock %} {% block menuitems %}
<li class="treeview">
<a href="#">
<i class="fa fa-bitcoin"></i> <span>Wallets</span>
@ -21,7 +19,9 @@
<ul class="treeview-menu">
{% for w in user_wallets %}
<li>
<a href="{{ url_for('wallet') }}?wal={{ w.id }}&usr={{ w.user }}"><i class="fa fa-bolt"></i> {{ w.name }}</a>
<a href="{{ url_for('wallet') }}?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a
>
</li>
{% endfor %}
<li><a onclick="sidebarmake()">Add a wallet +</a></li>
@ -34,21 +34,19 @@
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for extension in EXTENSIONS %}
{% if extension.code in user_ext %}
{% for extension in EXTENSIONS %} {% if extension.code in user_ext %}
<li>
<a href="{{ url_for(extension.code + '.index') }}?usr={{ user }}"><i class="fa fa-plus"></i> {{ extension.name }}</a>
<a href="{{ url_for(extension.code + '.index') }}?usr={{ user }}"
><i class="fa fa-plus"></i> {{ extension.name }}</a
>
</li>
{% endif %}
{% endfor %}
{% endif %} {% endfor %}
<li>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}">Manager</a></li>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}">Manager</a>
</li>
</ul>
</li>
{% endblock %}
{% block body %}
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
@ -56,14 +54,17 @@
<h1>
Events
<small>bitcoin tickets</small>
</h1>
<ol class="breadcrumb">
<li>
<a href="{{ url_for('wallet') }}?usr={{ user }}"><i class="fa fa-dashboard"></i> Home</a>
<a href="{{ url_for('wallet') }}?usr={{ user }}"
><i class="fa fa-dashboard"></i> Home</a
>
</li>
<li>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}"><li class="fa fa-dashboard">Extensions</li></a>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}"
><li class="fa fa-dashboard">Extensions</li></a
>
</li>
<li>
<i class="active" class="fa fa-dashboard">Lightning tickets</i>
@ -73,33 +74,32 @@
</section>
<style>
.datepicker-days {
background-color: #1f2234;
}
</style>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-6">
<!-- general form elements -->
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">Make a ticket wave</h3>
</div><!-- /.box-header -->
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form">
<div class="box-body">
<div class="form-group">
<label for="exampleInputEmail1">Ticket title</label>
<input id="tit" type="text" pattern="^[A-Za-z]+$" class="form-control" >
<input
id="tit"
type="text"
pattern="^[A-Za-z]+$"
class="form-control"
/>
</div>
<div class="form-group">
@ -118,65 +118,86 @@
</select>
</div>
<div class="form-group">
<label for="nooftickets">No. of tickets</label>
<input id="notickets" type="number" class="form-control" placeholder="10" max="86400"></input>
<input
id="notickets"
type="number"
class="form-control"
placeholder="10"
max="86400"
/>
</div>
</div>
<div class="form-group">
<label>Close date:</label>
<div class="form-group">
<input type="text" class="form-control" id="datepicker"></input>
<input type="text" class="form-control" id="datepicker" />
</div>
<div class="form-group">
<label for="prpertick">Price per ticket</label>
<input id="prtickets" type="number" class="form-control" placeholder="10"></input>
<input
id="prtickets"
type="number"
class="form-control"
placeholder="10"
/>
</div>
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<button onclick="postev()" type="button" class="btn btn-info">Create Wave</button><p style="color:red;" id="error"></p>
<button onclick="postev()" type="button" class="btn btn-info">
Create Wave
</button>
<p style="color: red;" id="error"></p>
</div>
</form>
</div></div></div>
</div>
</div>
</div>
<div class="col-md-6">
<!-- general form elements -->
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">Select a link</h3>
</div><!-- /.box-header -->
</div>
<!-- /.box-header -->
<form role="form">
<div class="box-body">
<div class="form-group">
<select class="form-control" id="waveselect" onchange="drawwithdraw()">
<select
class="form-control"
id="waveselect"
onchange="drawwithdraw()"
>
<option value="none" selected>
Select an Option
</option>
{% for w in user_ev %}
<option id="{{w.uni}}" value="{{w.tit}}-{{w.unireg}}-{{w.uni}}">{{w.tit}}-{{w.unireg}}-{{w.uni}}</option>
<option id="{{w.uni}}" value="{{w.tit}}-{{w.unireg}}-{{w.uni}}"
>{{w.tit}}-{{w.unireg}}-{{w.uni}}</option
>
{% endfor %}
</select>
</div>
<center> <br/><div id="qrcode" style="width:340px" ></div><br/><div style="width:75%;word-wrap: break-word;" id="qrcodetxt" ></div></center>
<center>
<br />
<div id="qrcode" style="width: 340px;"></div>
<br />
<div
style="width: 75%; word-wrap: break-word;"
id="qrcodetxt"
></div>
</center>
</div>
</form>
</div><!-- /.box -->
</div>
<!-- /.box -->
</div>
<div class="row">
@ -187,16 +208,19 @@
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<table id="pagnation" class="table table-bswearing anchorordered table-striped">
<table
id="pagnation"
class="table table-bswearing anchorordered table-striped"
>
<tr>
<th>Title</th>
<th style="width:15%">Amt</th>
<th style="width:15%">Sold</th>
<th style="width:15%">Closing</th>
<th style="width:15%">Price</th>
<th style="width:15%">Wallet</th>
<th style="width:10%">Edit</th>
<th style="width:10%">Del</th>
<th style="width: 15%;">Amt</th>
<th style="width: 15%;">Sold</th>
<th style="width: 15%;">Closing</th>
<th style="width: 15%;">Price</th>
<th style="width: 15%;">Wallet</th>
<th style="width: 10%;">Edit</th>
<th style="width: 10%;">Del</th>
</tr>
<tbody id="ticketwaves"></tbody>
</table>
@ -207,8 +231,6 @@
</div>
</div>
<div id="editlink"></div>
<!-- /.content -->
@ -338,7 +360,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<label for='exampleInputEmail1'>Link title</label>"+
"<input id='edittit' type='text' class='form-control' value='"+
evdetails.tit +
"'></input> </div>"+
"'> </div>"+
" </div>"+
"<div class='col-sm-1 col-md-8'>"+
@ -367,7 +389,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
" <label for='exampleInputPassword1'>No of tickets:</label>"+
" <input id='editnooftickets' type='number' class='form-control' placeholder='0' max='86400' value='"+
evdetails.notickets +
"'></input>"+
"'>"+
"</div> </div>"+
" <div class='col-sm-3 col-md-4'>"+
@ -375,14 +397,14 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<label for='exampleInputEmail1'>Price per ticket:</label>"+
" <input id='editprtick' type='number' class='form-control' placeholder='1' value='"+
evdetails.prtick +
"'></input>"+
"'>"+
" </div></div>"+
" <div class='col-sm-3 col-md-4'>"+
" <div class='input-group date'>"+
" <label for='exampleInputEmail1'>Close date:</label>"+
" <input id='datepicker2' type='text' class='form-control' placeholder='1' value='"+
evdetails.cldate +
"'></input>"+
"'>"+
" </div></div>"+
@ -474,7 +496,6 @@ function drawwithdraw() {
document.getElementById("qrcode").style.padding = "20px";
}
</script>
</div>
{% endblock %}

184
lnbits/extensions/events/templates/events/registration.html

@ -202,15 +202,11 @@
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background:
#1f2234;
background: #1f2234;
}
body {
@ -224,60 +220,50 @@ body {
}
.skin-blue .main-header .navbar {
background-color:
#2e507d;
background-color: #2e507d;
}
.content-wrapper, .right-side {
background-color:
#1f2234;
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color:
#1f2234;
color:
#fff;
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color:
#4b646f;
background:
#1f2234;
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
background:
#1f2234;
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background:
#1f2234;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid
transparent;
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background:
#3e355a;
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color:
#3e355a;
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
@ -290,28 +276,36 @@ background:
background-color: #1f2234;
}
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
background-color:
#1f2234 !important;
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger, .alert-error {
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid
#fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus {
color:
#f6f6f6;
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
background-color:
#3e355a !important;
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
@ -321,19 +315,15 @@ background:
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color:
#333646;
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
@ -348,7 +338,9 @@ color: #8964a9;
border-bottom: none;
}
a:hover, a:active, a:focus {
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
@ -358,48 +350,35 @@ a:hover, a:active, a:focus {
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color:
#333646;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color:
#333646;
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color:
#3e355a;
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
@ -421,7 +400,6 @@ background-color:
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
@ -433,8 +411,6 @@ background-color:
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;">
<!-- Sidebar user panel -->
</section>
<!-- /.sidebar -->
</aside>
@ -447,21 +423,26 @@ background-color:
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content"><br/><br/>
<center><h1 style="font-size:500%">{{ user_ev[0][6] }}</h1></center>
<section class="content">
<br /><br />
<center><h1 style="font-size: 500%;">{{ user_ev[0][6] }}</h1></center>
<br /><br /><br />
<div class='modal fade sends' tabindex='-1' role='dialog' aria-labelledby='myLargeModalLabel' aria-hidden='true'>
<div class='modal-dialog' >
<div id='scantickets' style='padding: 0 10px 0 10px;'>
</div></div></div>
<div
class="modal fade sends"
tabindex="-1"
role="dialog"
aria-labelledby="myLargeModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div id="scantickets" style="padding: 0 10px 0 10px;"></div>
</div>
</div>
<center>
<button
@ -475,24 +456,22 @@ background-color:
</button>
</center>
<div id="scantickets"></div>
<br /><br /><br />
<div id="qrcodetxt"></div> <br/></center>
<div id="qrcodetxt"></div>
<br />
<br /><br /><br />
<center>
<div class="row" style="width:80%;margin-top:80px">
<div class="row" style="width: 80%; margin-top: 80px;">
<style>
.ema, button:focus .txt {
.ema,
button:focus .txt {
display: none;
}
button:focus .ema {
display: block;
}
</style>
<div class="box">
<div class="box-header">
@ -500,12 +479,15 @@ button:focus .ema {
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<table id="pagnation" class="table table-bswearing anchorordered table-striped">
<table
id="pagnation"
class="table table-bswearing anchorordered table-striped"
>
<tr>
<th style="width:20%">Name</th>
<th style="width:20%">Email</th>
<th style="width:50%">Ticket</th>
<th style="width:10%">Registered</th>
<th style="width: 20%;">Name</th>
<th style="width: 20%;">Email</th>
<th style="width: 50%;">Ticket</th>
<th style="width: 10%;">Registered</th>
</tr>
<tbody id="ticketwaves"></tbody>
</table>
@ -513,12 +495,12 @@ button:focus .ema {
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</center>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</body>
@ -684,7 +666,5 @@ if(thehash != null){
document.getElementById('qrcodetxt').innerHTML = "<center><h1>" + name + " is registered!</h1></center>"
}
</script>
</html>

159
lnbits/extensions/events/templates/events/ticket.html

@ -202,15 +202,11 @@
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background:
#1f2234;
background: #1f2234;
}
body {
@ -224,60 +220,50 @@ body {
}
.skin-blue .main-header .navbar {
background-color:
#2e507d;
background-color: #2e507d;
}
.content-wrapper, .right-side {
background-color:
#1f2234;
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color:
#1f2234;
color:
#fff;
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color:
#4b646f;
background:
#1f2234;
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
background:
#1f2234;
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background:
#1f2234;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid
transparent;
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background:
#3e355a;
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color:
#3e355a;
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
@ -290,28 +276,36 @@ background:
background-color: #1f2234;
}
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
background-color:
#1f2234 !important;
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger, .alert-error {
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid
#fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus {
color:
#f6f6f6;
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
background-color:
#3e355a !important;
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
@ -321,19 +315,15 @@ background:
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color:
#333646;
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
@ -348,7 +338,9 @@ color: #8964a9;
border-bottom: none;
}
a:hover, a:active, a:focus {
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
@ -358,48 +350,35 @@ a:hover, a:active, a:focus {
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color:
#333646;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color:
#333646;
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color:
#3e355a;
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
@ -421,7 +400,6 @@ background-color:
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
@ -431,9 +409,7 @@ background-color:
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;">
</section>
<section class="sidebar" style="height: auto;"></section>
<!-- /.sidebar -->
</aside>
@ -445,30 +421,37 @@ background-color:
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content"><br/><br/>
<center><h2 style="width:70%;font-size:400%" >Bookmark/Screenshot this page. <br/>It is your ticket!</h2></center>
<center> <div style="width:340px;background-color:white;padding:20px"id="qrcode"></div></center>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<section class="content">
<br /><br />
<center>
<h2 style="width: 70%; font-size: 400%;">
Bookmark/Screenshot this page. <br />It is your ticket!
</h2>
</center>
<center>
<div
style="width: 340px; background-color: white; padding: 20px;"
id="qrcode"
></div>
</center>
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</body>
<script>
new QRCode(document.getElementById('qrcode'), {
text: "{{ticket}}",
text: '{{ticket}}',
width: 300,
height: 300,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
})
</script>
</html>

39
lnbits/extensions/example/templates/example/index.html

@ -1,15 +1,18 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block page %}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<q-card>
<q-card-section>
<h5 class="text-subtitle1 q-mt-none q-mb-md">Frameworks used by LNbits</h5>
<q-list>
<q-item v-for="tool in tools" :key="tool.name" tag="a" :href="tool.url" target="_blank">
{% raw %} <!-- with raw Flask won't try to interpret the Vue moustaches -->
<q-item
v-for="tool in tools"
:key="tool.name"
tag="a"
:href="tool.url"
target="_blank"
>
{% raw %}
<!-- with raw Flask won't try to interpret the Vue moustaches -->
<q-item-section>
<q-item-label>{{ tool.name }}</q-item-label>
<q-item-label caption>{{ tool.language }}</q-item-label>
@ -18,14 +21,14 @@
</q-item>
</q-list>
<q-separator class="q-my-lg"></q-separator>
<p>A magical "g" is always available, with info about the user, wallets and extensions:</p>
<p>
A magical "g" is always available, with info about the user, wallets and
extensions:
</p>
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code>
</q-card-section>
</q-card>
{% endblock %}
{% block scripts %}
{{ window_vars(user) }}
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
new Vue({
el: '#vue',
@ -33,10 +36,10 @@
data: function () {
return {
tools: []
};
}
},
created: function () {
var self = this;
var self = this
// axios is available for making requests
axios({
@ -46,9 +49,9 @@
'X-example-header': 'not-used'
}
}).then(function (response) {
self.tools = response.data;
});
self.tools = response.data
})
}
});
})
</script>
{% endblock %}

20
lnbits/extensions/paywall/templates/paywall/_api_docs.html

@ -6,23 +6,23 @@
>
<q-expansion-item group="api" dense expand-separator label="List paywalls">
<q-card>
<q-card-section>
</q-card-section>
<q-card-section> </q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create a paywall">
<q-card>
<q-card-section>
</q-card-section>
<q-card-section> </q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Delete a paywall" class="q-pb-md">
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a paywall"
class="q-pb-md"
>
<q-card>
<q-card-section>
</q-card-section>
<q-card-section> </q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

95
lnbits/extensions/paywall/templates/paywall/display.html

@ -1,29 +1,39 @@
{% extends "public.html" %}
{% block page %}
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<h5 class="text-subtitle1 q-my-none">{{ paywall.memo }}</h5>
<strong class="text-purple">Price: <lnbits-fsat :amount="{{ paywall.amount }}"></lnbits-fsat> sat</strong>
<strong class="text-purple"
>Price:
<lnbits-fsat :amount="{{ paywall.amount }}"></lnbits-fsat> sat</strong
>
<q-separator class="q-my-lg"></q-separator>
<div v-if="paymentReq">
<a :href="'lightning:' + paymentReq">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="paymentReq" :options="{width: 800}" class="rounded-borders"></qrcode>
<qrcode
:value="paymentReq"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(paymentReq)">Copy invoice</q-btn>
<q-btn outline color="grey" @click="copyText(paymentReq)"
>Copy invoice</q-btn
>
</div>
</div>
<div v-if="redirectUrl">
<p>You can access the URL behind this paywall:<br>
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong></p>
<p>
You can access the URL behind this paywall:<br />
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong>
</p>
<div class="row q-mt-lg">
<q-btn outline color="grey" type="a" :href="redirectUrl">Open URL</q-btn>
<q-btn outline color="grey" type="a" :href="redirectUrl"
>Open URL</q-btn
>
</div>
</div>
</q-card-section>
@ -37,12 +47,10 @@
</q-card>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode);
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
@ -51,57 +59,64 @@
return {
paymentReq: null,
redirectUrl: null
};
}
},
methods: {
getInvoice: function () {
var self = this;
var self = this
axios.get(
'/paywall/api/v1/paywalls/{{ paywall.id }}/invoice'
).then(function (response) {
self.paymentReq = response.data.payment_request;
axios
.get('/paywall/api/v1/paywalls/{{ paywall.id }}/invoice')
.then(function (response) {
self.paymentReq = response.data.payment_request
dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
});
})
paymentChecker = setInterval(function () {
axios.post(
axios
.post(
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice',
{checking_id: response.data.checking_id}
).then(function (res) {
)
.then(function (res) {
if (res.data.paid) {
clearInterval(paymentChecker);
dismissMsg();
self.redirectUrl = res.data.url;
self.$q.localStorage.set('lnbits.paywall.{{ paywall.id }}', res.data.url);
clearInterval(paymentChecker)
dismissMsg()
self.redirectUrl = res.data.url
self.$q.localStorage.set(
'lnbits.paywall.{{ paywall.id }}',
res.data.url
)
self.$q.notify({
type: 'positive',
message: 'Payment received!',
icon: null
});
})
}
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
}, 2000);
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, 2000)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}
},
created: function () {
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}');
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}')
if (url) {
this.redirectUrl = url;
this.redirectUrl = url
} else {
this.getInvoice();
};
this.getInvoice()
}
}
});
})
</script>
{% endblock %}

212
lnbits/extensions/paywall/templates/paywall/index.html

@ -1,14 +1,12 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block page %}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true">New paywall</q-btn>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
>New paywall</q-btn
>
</q-card-section>
</q-card>
@ -22,20 +20,19 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="paywalls"
row-key="id"
:columns="paywallsTable.columns"
:pagination.sync="paywallsTable.pagination">
:pagination.sync="paywallsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -44,18 +41,39 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.displayUrl" target="_blank"></q-btn>
<q-btn unelevated dense size="xs" icon="link" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.url" target="_blank"></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.displayUrl"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="link"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.url"
target="_blank"
></q-btn>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deletePaywall(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deletePaywall(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -82,41 +100,62 @@
<q-dialog v-model="formDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="createPaywall" class="q-gutter-md">
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input filled dense
<q-input
filled
dense
v-model.trim="formDialog.data.url"
type="url"
label="Target URL *"></q-input>
<q-input filled dense
label="Target URL *"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.amount"
type="number"
label="Amount (sat) *"></q-input>
<q-input filled dense
label="Amount (sat) *"
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.memo"
label="Memo"
placeholder="LNbits invoice"></q-input>
placeholder="LNbits invoice"
></q-input>
<div class="row q-mt-lg">
<q-btn unelevated
<q-btn
unelevated
color="deep-purple"
:disable="formDialog.data.amount == null || formDialog.data.amount < 0 || formDialog.data.url == null"
type="submit">Create paywall</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
type="submit"
>Create paywall</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %}
{% block scripts %}
{{ window_vars(user) }}
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapPaywall = function (obj) {
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
obj.displayUrl = ['/paywall/', obj.id].join('');
return obj;
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.displayUrl = ['/paywall/', obj.id].join('')
return obj
}
new Vue({
@ -129,11 +168,21 @@
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'},
{name: 'date', align: 'left', label: 'Date', field: 'date', sortable: true},
{
name: 'amount', align: 'right', label: 'Amount (sat)', field: 'fsat', sortable: true,
name: 'date',
align: 'left',
label: 'Date',
field: 'date',
sortable: true
},
{
name: 'amount',
align: 'right',
label: 'Amount (sat)',
field: 'fsat',
sortable: true,
sort: function (a, b, rowA, rowB) {
return rowA.amount - rowB.amount;
return rowA.amount - rowB.amount
}
}
],
@ -145,70 +194,81 @@
show: false,
data: {}
}
};
}
},
methods: {
getPaywalls: function () {
var self = this;
var self = this
LNbits.api.request(
LNbits.api
.request(
'GET',
'/paywall/api/v1/paywalls?all_wallets',
this.g.user.wallets[0].inkey
).then(function (response) {
)
.then(function (response) {
self.paywalls = response.data.map(function (obj) {
return mapPaywall(obj);
});
});
return mapPaywall(obj)
})
})
},
createPaywall: function () {
var data = {
url: this.formDialog.data.url,
memo: this.formDialog.data.memo,
amount: this.formDialog.data.amount
};
var self = this;
}
var self = this
LNbits.api.request(
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls',
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet}).inkey,
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
).then(function (response) {
self.paywalls.push(mapPaywall(response.data));
self.formDialog.show = false;
self.formDialog.data = {};
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
)
.then(function (response) {
self.paywalls.push(mapPaywall(response.data))
self.formDialog.show = false
self.formDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deletePaywall: function (paywallId) {
var self = this;
var paywall = _.findWhere(this.paywalls, {id: paywallId});
var self = this
var paywall = _.findWhere(this.paywalls, {id: paywallId})
LNbits.utils.confirmDialog(
'Are you sure you want to delete this paywall link?'
).onOk(function () {
LNbits.api.request(
LNbits.utils
.confirmDialog('Are you sure you want to delete this paywall link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/paywall/api/v1/paywalls/' + paywallId,
_.findWhere(self.g.user.wallets, {id: paywall.wallet}).inkey
).then(function (response) {
self.paywalls = _.reject(self.paywalls, function (obj) { return obj.id == paywallId; });
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
});
)
.then(function (response) {
self.paywalls = _.reject(self.paywalls, function (obj) {
return obj.id == paywallId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls);
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getPaywalls();
this.getPaywalls()
}
}
});
})
</script>
{% endblock %}

61
lnbits/extensions/tpos/templates/tpos/_api_docs.html

@ -4,49 +4,84 @@
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="List all users TPoS">
<q-expansion-item
group="api"
dense
expand-separator
label="List all users TPoS"
>
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /tpos/api/v1/tposs</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"currency": &lt;string&gt;, "id": &lt;string&gt;, "name": &lt;string&gt;, "wallet": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"currency": &lt;string&gt;, "id": &lt;string&gt;, "name":
&lt;string&gt;, "wallet": &lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}tpos/api/v1/tposs -H "X-Api-Key: &lt;invoice_key&gt;" </code>
<code
>curl -X GET {{ request.url_root }}tpos/api/v1/tposs -H "X-Api-Key:
&lt;invoice_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create a TPoS">
<q-card>
<q-card-section>
<code><span class="text-light-green">POST</span> /tpos/api/v1/tposs</code>
<code
><span class="text-light-green">POST</span> /tpos/api/v1/tposs</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"name": &lt;string&gt;, "currency": &lt;string*ie USD*&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"currency": &lt;string&gt;, "id": &lt;string&gt;, "name": &lt;string&gt;, "wallet": &lt;string&gt;}</code>
<code
>{"name": &lt;string&gt;, "currency": &lt;string*ie USD*&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"currency": &lt;string&gt;, "id": &lt;string&gt;, "name":
&lt;string&gt;, "wallet": &lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}tpos/api/v1/tposs -d '{"name": &lt;string&gt;, "currency": &lt;string&gt;}' -H "Content-type: application/json" -H "X-Api-Key: &lt;admin_key&gt;"
<code
>curl -X POST {{ request.url_root }}tpos/api/v1/tposs -d '{"name":
&lt;string&gt;, "currency": &lt;string&gt;}' -H "Content-type:
application/json" -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Delete a TPoS" class="q-pb-md">
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a TPoS"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code><span class="text-light-green">DELETE</span> /tpos/api/v1/tposs/&lt;tpos_id&gt;</code>
<code
><span class="text-light-green">DELETE</span>
/tpos/api/v1/tposs/&lt;tpos_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 NO_CONTENT</h5>
<code></code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X DELETE {{ request.url_root }}tpos/api/v1/tposs/&lt;tpos_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
<code
>curl -X DELETE {{ request.url_root
}}tpos/api/v1/tposs/&lt;tpos_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>

19
lnbits/extensions/tpos/templates/tpos/_tpos.html

@ -1,11 +1,18 @@
<q-expansion-item
group="extras"
icon="info"
label="About TPoS">
<q-expansion-item group="extras" icon="info" label="About TPoS">
<q-card>
<q-card-section>
<p>Thiago's Point of Sale is a secure, mobile-ready, instant and shareable point of sale terminal (PoS) for merchants. The PoS is linked to your LNbits wallet but completely air-gapped so users can ONLY create invoices. To share the TPoS hit the hash on the terminal.</p>
<small>Created by <a href="https://github.com/talvasconcelos" target="_blank">Tiago Vasconcelos</a>.</small>
<p>
Thiago's Point of Sale is a secure, mobile-ready, instant and shareable
point of sale terminal (PoS) for merchants. The PoS is linked to your
LNbits wallet but completely air-gapped so users can ONLY create
invoices. To share the TPoS hit the hash on the terminal.
</p>
<small
>Created by
<a href="https://github.com/talvasconcelos" target="_blank"
>Tiago Vasconcelos</a
>.</small
>
</q-card-section>
</q-card>
</q-expansion-item>

372
lnbits/extensions/tpos/templates/tpos/index.html

@ -1,14 +1,12 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block page %}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true">New TPoS</q-btn>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
>New TPoS</q-btn
>
</q-card-section>
</q-card>
@ -22,20 +20,19 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="tposs"
row-key="id"
:columns="tpossTable.columns"
:pagination.sync="tpossTable.pagination">
:pagination.sync="tpossTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -45,17 +42,29 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.tpos" target="_blank"></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.tpos"
target="_blank"
></q-btn>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteTPoS(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteTPoS(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -81,43 +90,58 @@
</q-card>
</div>
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;">
<q-form @submit="createTPoS" class="q-gutter-md">
<q-input filled dense
<q-input
filled
dense
v-model.trim="formDialog.data.name"
label="Name"
placeholder="Tiago's PoS"></q-input>
<q-select filled dense
emit-value v-model="formDialog.data.wallet"
placeholder="Tiago's PoS"
></q-input>
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"></q-select>
<q-select filled dense
emit-value v-model="formDialog.data.currency"
label="Wallet *"
></q-select>
<q-select
filled
dense
emit-value
v-model="formDialog.data.currency"
:options="currencyOptions"
label="Currency *"></q-select>
label="Currency *"
></q-select>
<div class="row q-mt-lg">
<q-btn unelevated
<q-btn
unelevated
color="deep-purple"
:disable="formDialog.data.currency == null || formDialog.data.name == null"
type="submit">Create TPoS</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
type="submit"
>Create TPoS</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %}
{% block scripts %}
{{ window_vars(user) }}
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapTPoS = function (obj) {
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
obj.tpos = ['/tpos/', obj.id].join('');
return obj;
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.tpos = ['/tpos/', obj.id].join('')
return obj
}
new Vue({
@ -127,21 +151,188 @@
return {
tposs: [],
currencyOptions: [
'USD','EUR','GBP','AED','AFN','ALL','AMD','ANG','AOA','ARS','AUD','AWG','AZN','BAM','BBD','BDT','BGN','BHD',
'BIF','BMD','BND','BOB','BRL','BSD','BTN','BWP','BYN','BZD','CAD','CDF','CHF','CLF','CLP','CNH','CNY','COP',
'CRC','CUC','CUP','CVE','CZK','DJF','DKK','DOP','DZD','EGP','ERN','ETB','EUR','FJD','FKP','GBP','GEL','GGP',
'GHS','GIP','GMD','GNF','GTQ','GYD','HKD','HNL','HRK','HTG','HUF','IDR','ILS','IMP','INR','IQD','IRR','ISK',
'JEP','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL',
'LYD','MAD','MDL','MGA','MKD','MMK','MNT','MOP','MRO','MUR','MVR','MWK','MXN','MYR','MZN','NAD','NGN','NIO','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PYG','QAR','RON','RSD','RUB','RWF','SAR','SBD',
'SCR','SDG','SEK','SGD','SHP','SLL','SOS','SRD','SSP','STD','SVC','SYP','SZL','THB','TJS','TMT','TND','TOP',
'TRY','TTD','TWD','TZS','UAH','UGX','USD','UYU','UZS','VEF','VES','VND','VUV','WST','XAF','XAG','XAU','XCD',
'XDR','XOF','XPD','XPF','XPT','YER','ZAR','ZMW','ZWL'
'USD',
'EUR',
'GBP',
'AED',
'AFN',
'ALL',
'AMD',
'ANG',
'AOA',
'ARS',
'AUD',
'AWG',
'AZN',
'BAM',
'BBD',
'BDT',
'BGN',
'BHD',
'BIF',
'BMD',
'BND',
'BOB',
'BRL',
'BSD',
'BTN',
'BWP',
'BYN',
'BZD',
'CAD',
'CDF',
'CHF',
'CLF',
'CLP',
'CNH',
'CNY',
'COP',
'CRC',
'CUC',
'CUP',
'CVE',
'CZK',
'DJF',
'DKK',
'DOP',
'DZD',
'EGP',
'ERN',
'ETB',
'EUR',
'FJD',
'FKP',
'GBP',
'GEL',
'GGP',
'GHS',
'GIP',
'GMD',
'GNF',
'GTQ',
'GYD',
'HKD',
'HNL',
'HRK',
'HTG',
'HUF',
'IDR',
'ILS',
'IMP',
'INR',
'IQD',
'IRR',
'ISK',
'JEP',
'JMD',
'JOD',
'JPY',
'KES',
'KGS',
'KHR',
'KMF',
'KPW',
'KRW',
'KWD',
'KYD',
'KZT',
'LAK',
'LBP',
'LKR',
'LRD',
'LSL',
'LYD',
'MAD',
'MDL',
'MGA',
'MKD',
'MMK',
'MNT',
'MOP',
'MRO',
'MUR',
'MVR',
'MWK',
'MXN',
'MYR',
'MZN',
'NAD',
'NGN',
'NIO',
'NOK',
'NPR',
'NZD',
'OMR',
'PAB',
'PEN',
'PGK',
'PHP',
'PKR',
'PLN',
'PYG',
'QAR',
'RON',
'RSD',
'RUB',
'RWF',
'SAR',
'SBD',
'SCR',
'SDG',
'SEK',
'SGD',
'SHP',
'SLL',
'SOS',
'SRD',
'SSP',
'STD',
'SVC',
'SYP',
'SZL',
'THB',
'TJS',
'TMT',
'TND',
'TOP',
'TRY',
'TTD',
'TWD',
'TZS',
'UAH',
'UGX',
'USD',
'UYU',
'UZS',
'VEF',
'VES',
'VND',
'VUV',
'WST',
'XAF',
'XAG',
'XAU',
'XCD',
'XDR',
'XOF',
'XPD',
'XPF',
'XPT',
'YER',
'ZAR',
'ZMW',
'ZWL'
],
tpossTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
{name: 'currency', align: 'left', label: 'Currency', field: 'currency'}
{
name: 'currency',
align: 'left',
label: 'Currency',
field: 'currency'
}
],
pagination: {
rowsPerPage: 10
@ -151,71 +342,82 @@
show: false,
data: {}
}
};
}
},
methods: {
closeFormDialog: function () {
this.formDialog.data = {};
this.formDialog.data = {}
},
getTPoSs: function () {
var self = this;
var self = this
LNbits.api.request(
LNbits.api
.request(
'GET',
'/tpos/api/v1/tposs?all_wallets',
this.g.user.wallets[0].inkey
).then(function (response) {
)
.then(function (response) {
self.tposs = response.data.map(function (obj) {
return mapTPoS(obj);
});
});
return mapTPoS(obj)
})
})
},
createTPoS: function () {
var data = {
name: this.formDialog.data.name,
currency: this.formDialog.data.currency
};
var self = this;
}
var self = this
LNbits.api.request(
LNbits.api
.request(
'POST',
'/tpos/api/v1/tposs',
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet}).inkey,
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
).then(function (response) {
self.tposs.push(mapTPoS(response.data));
self.formDialog.show = false;
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
)
.then(function (response) {
self.tposs.push(mapTPoS(response.data))
self.formDialog.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteTPoS: function (tposId) {
var self = this;
var tpos = _.findWhere(this.tposs, {id: tposId});
var self = this
var tpos = _.findWhere(this.tposs, {id: tposId})
LNbits.utils.confirmDialog(
'Are you sure you want to delete this TPoS?'
).onOk(function () {
LNbits.api.request(
LNbits.utils
.confirmDialog('Are you sure you want to delete this TPoS?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/tpos/api/v1/tposs/' + tposId,
_.findWhere(self.g.user.wallets, {id: tpos.wallet}).adminkey
).then(function (response) {
self.tposs = _.reject(self.tposs, function (obj) { return obj.id == tposId; });
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
});
)
.then(function (response) {
self.tposs = _.reject(self.tposs, function (obj) {
return obj.id == tposId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs);
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getTPoSs();
this.getTPoSs()
}
}
});
})
</script>
{% endblock %}

244
lnbits/extensions/tpos/templates/tpos/tpos.html

@ -1,11 +1,5 @@
{% extends "public.html" %}
{% block toolbar_title %}{{ tpos.name }}{% endblock %}
{% block footer %}{% endblock %}
{% block page_container %}
{% extends "public.html" %} {% block toolbar_title %}{{ tpos.name }}{% endblock
%} {% block footer %}{% endblock %} {% block page_container %}
<q-page-container>
<q-page>
<q-page-sticky v-if="exchangeRate" expand position="top">
@ -22,57 +16,86 @@
<div class="row justify-center full-width">
<div class="col-12 col-sm-8 col-md-6 col-lg-4">
<div class="keypad q-pa-sm">
<q-btn unelevated
@click="stack.push(1)"
size="xl" color="grey-8">1</q-btn>
<q-btn unelevated
@click="stack.push(2)"
size="xl" color="grey-8">2</q-btn>
<q-btn unelevated
@click="stack.push(3)"
size="xl" color="grey-8">3</q-btn>
<q-btn unelevated
<q-btn unelevated @click="stack.push(1)" size="xl" color="grey-8"
>1</q-btn
>
<q-btn unelevated @click="stack.push(2)" size="xl" color="grey-8"
>2</q-btn
>
<q-btn unelevated @click="stack.push(3)" size="xl" color="grey-8"
>3</q-btn
>
<q-btn
unelevated
@click="stack = []"
size="xl" color="pink" class="btn-cancel">C</q-btn>
<q-btn unelevated
@click="stack.push(4)"
size="xl" color="grey-8">4</q-btn>
<q-btn unelevated
@click="stack.push(5)"
size="xl" color="grey-8">5</q-btn>
<q-btn unelevated
@click="stack.push(6)"
size="xl" color="grey-8">6</q-btn>
<q-btn unelevated
@click="stack.push(7)"
size="xl" color="grey-8">7</q-btn>
<q-btn unelevated
@click="stack.push(8)"
size="xl" color="grey-8">8</q-btn>
<q-btn unelevated
@click="stack.push(9)"
size="xl" color="grey-8">9</q-btn>
<q-btn unelevated
size="xl"
color="pink"
class="btn-cancel"
>C</q-btn
>
<q-btn unelevated @click="stack.push(4)" size="xl" color="grey-8"
>4</q-btn
>
<q-btn unelevated @click="stack.push(5)" size="xl" color="grey-8"
>5</q-btn
>
<q-btn unelevated @click="stack.push(6)" size="xl" color="grey-8"
>6</q-btn
>
<q-btn unelevated @click="stack.push(7)" size="xl" color="grey-8"
>7</q-btn
>
<q-btn unelevated @click="stack.push(8)" size="xl" color="grey-8"
>8</q-btn
>
<q-btn unelevated @click="stack.push(9)" size="xl" color="grey-8"
>9</q-btn
>
<q-btn
unelevated
:disabled="amount == 0"
@click="showInvoice()"
size="xl" color="green" class="btn-confirm">OK</q-btn>
<q-btn unelevated
size="xl"
color="green"
class="btn-confirm"
>OK</q-btn
>
<q-btn
unelevated
@click="stack.splice(-1, 1)"
size="xl" color="grey-7">DEL</q-btn>
<q-btn unelevated
@click="stack.push(0)"
size="xl" color="grey-8">0</q-btn>
<q-btn unelevated
size="xl"
color="grey-7"
>DEL</q-btn
>
<q-btn unelevated @click="stack.push(0)" size="xl" color="grey-8"
>0</q-btn
>
<q-btn
unelevated
@click="urlDialog.show = true"
size="xl" color="grey-7">#</q-btn>
size="xl"
color="grey-7"
>#</q-btn
>
</div>
</div>
</div>
</q-page-sticky>
<q-dialog v-model="invoiceDialog.show" position="top" @hide="closeInvoiceDialog">
<q-card v-if="invoiceDialog.data" class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-dialog
v-model="invoiceDialog.show"
position="top"
@hide="closeInvoiceDialog"
>
<q-card
v-if="invoiceDialog.data"
class="q-pa-lg q-pt-xl lnbits__dialog-card"
>
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="invoiceDialog.data.payment_request" :options="{width: 800}" class="rounded-borders"></qrcode>
<qrcode
:value="invoiceDialog.data.payment_request"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<div class="text-center">
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3>
@ -88,22 +111,31 @@
<q-dialog v-model="urlDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode value="{{ request.url }}" :options="{width: 800}" class="rounded-borders"></qrcode>
<qrcode
value="{{ request.url }}"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<div class="text-center q-mb-xl">
<p style="word-break: break-all"><strong>{{ tpos.name }}</strong><br>{{ request.url }}</p>
<p style="word-break: break-all;">
<strong>{{ tpos.name }}</strong><br />{{ request.url }}
</p>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText('{{ request.url }}', 'TPoS URL copied to clipboard!')">Copy URL</q-btn>
<q-btn
outline
color="grey"
@click="copyText('{{ request.url }}', 'TPoS URL copied to clipboard!')"
>Copy URL</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
</q-page>
</q-page-container>
{% endblock %}
{% block styles %}
{% endblock %} {% block styles %}
<style>
.keypad {
display: grid;
@ -114,16 +146,15 @@
.keypad .btn {
height: 100%;
}
.btn-cancel, .btn-confirm {
.btn-cancel,
.btn-confirm {
grid-row: auto/span 2;
}
</style>
{% endblock %}
{% block scripts %}
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode);
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
@ -143,83 +174,92 @@
urlDialog: {
show: false
}
};
}
},
computed: {
amount: function () {
if (!this.stack.length) return 0.00;
return (Number(this.stack.join('')) / 100).toFixed(2);
if (!this.stack.length) return 0.0
return (Number(this.stack.join('')) / 100).toFixed(2)
},
famount: function () {
return LNbits.utils.formatCurrency(this.amount, this.currency);
return LNbits.utils.formatCurrency(this.amount, this.currency)
},
sat: function () {
if (!this.exchangeRate) return 0;
return Math.ceil((this.amount / this.exchangeRate) * 100000000);
if (!this.exchangeRate) return 0
return Math.ceil((this.amount / this.exchangeRate) * 100000000)
},
fsat: function () {
return LNbits.utils.formatSat(this.sat);
return LNbits.utils.formatSat(this.sat)
}
},
methods: {
closeInvoiceDialog: function () {
this.stack = [];
var dialog = this.invoiceDialog;
this.stack = []
var dialog = this.invoiceDialog
setTimeout(function () {
clearInterval(dialog.paymentChecker);
dialog.dismissMsg();
}, 3000);
clearInterval(dialog.paymentChecker)
dialog.dismissMsg()
}, 3000)
},
showInvoice: function () {
var self = this;
var dialog = this.invoiceDialog;
var self = this
var dialog = this.invoiceDialog
axios.post(
'/tpos/api/v1/tposs/' + this.tposId + '/invoices/',
{amount: this.sat}
).then(function (response) {
dialog.data = response.data;
dialog.show = true;
axios
.post('/tpos/api/v1/tposs/' + this.tposId + '/invoices/', {
amount: this.sat
})
.then(function (response) {
dialog.data = response.data
dialog.show = true
dialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
});
})
dialog.paymentChecker = setInterval(function () {
axios.get(
'/tpos/api/v1/tposs/' + self.tposId + '/invoices/' + response.data.checking_id
).then(function (res) {
axios
.get(
'/tpos/api/v1/tposs/' +
self.tposId +
'/invoices/' +
response.data.checking_id
)
.then(function (res) {
if (res.data.paid) {
clearInterval(dialog.paymentChecker);
dialog.dismissMsg();
dialog.show = false;
clearInterval(dialog.paymentChecker)
dialog.dismissMsg()
dialog.show = false
self.$q.notify({
type: 'positive',
message: self.fsat + ' sat received!',
icon: null
});
})
}
});
}, 2000);
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
})
}, 2000)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
getRates: function () {
var self = this;
axios.get("https://api.opennode.co/v1/rates").then(function (response) {
self.exchangeRate = response.data.data['BTC' + self.currency][self.currency];
});
var self = this
axios.get('https://api.opennode.co/v1/rates').then(function (response) {
self.exchangeRate =
response.data.data['BTC' + self.currency][self.currency]
})
}
},
created: function () {
var getRates = this.getRates;
getRates();
setInterval(function () { getRates(); }, 20000);
var getRates = this.getRates
getRates()
setInterval(function () {
getRates()
}, 20000)
}
});
})
</script>
{% endblock %}

172
lnbits/extensions/usermanager/templates/usermanager/_api_docs.html

@ -1,4 +1,3 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
@ -7,13 +6,21 @@
>
<q-card>
<q-card-section>
<h5 class="text-subtitle1 q-my-none">User Manager: Make and manager users/wallets</h5>
<p>To help developers use LNbits to manage their users, the User Manager extension allows the creation and management of users and wallets. <br/>For example, a games developer may be developing a game that needs each user to have their own wallet, LNbits can be included in the develpoers stack as the user and wallet manager.<br/>
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p>
</q-card>
</q-card-section>
<h5 class="text-subtitle1 q-my-none">
User Manager: Make and manager users/wallets
</h5>
<p>
To help developers use LNbits to manage their users, the User Manager
extension allows the creation and management of users and wallets.
<br />For example, a games developer may be developing a game that needs
each user to have their own wallet, LNbits can be included in the
develpoers stack as the user and wallet manager.<br />
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
>
</p>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="extras"
@ -21,59 +28,109 @@
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="GET users">
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /usermanager/api/v1/users</code>
<code
><span class="text-light-blue">GET</span>
/usermanager/api/v1/users</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>JSON list of users</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}usermanager/api/v1/users -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" </code>
<code
>curl -X GET {{ request.url_root }}usermanager/api/v1/users -H
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET wallets">
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /usermanager/api/v1/wallets/&lt;user_id&gt;</code>
<code
><span class="text-light-blue">GET</span>
/usermanager/api/v1/wallets/&lt;user_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>JSON wallet data</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}usermanager/api/v1/wallets/&lt;user_id&gt; -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" </code>
<code
>curl -X GET {{ request.url_root
}}usermanager/api/v1/wallets/&lt;user_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET transactions">
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /usermanager/api/v1/wallets&lt;wallet_id&gt;</code>
<code
><span class="text-light-blue">GET</span>
/usermanager/api/v1/wallets&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>JSON a wallets transactions</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}usermanager/api/v1/wallets&lt;wallet_id&gt; -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" </code>
<code
>curl -X GET {{ request.url_root
}}usermanager/api/v1/wallets&lt;wallet_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="POST user + initial wallet">
<q-expansion-item
group="api"
dense
expand-separator
label="POST user + initial wallet"
>
<q-card>
<q-card-section>
<code><span class="text-light-green">POST</span> /usermanager/api/v1/users</code>
<code
><span class="text-light-green">POST</span>
/usermanager/api/v1/users</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;, "Content-type": "application/json"}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json) - "admin_id" is a YOUR user ID</h5>
<code>{"admin_id": &lt;string&gt;, "user_name": &lt;string&gt;, "wallet_name": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"checking_id": &lt;string&gt;,"payment_request": &lt;string&gt;}</code>
<code
>{"X-Api-Key": &lt;string&gt;, "Content-type":
"application/json"}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Body (application/json) - "admin_id" is a YOUR user ID
</h5>
<code
>{"admin_id": &lt;string&gt;, "user_name": &lt;string&gt;,
"wallet_name": &lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"checking_id": &lt;string&gt;,"payment_request":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}usermanager/api/v1/users -d '{"admin_id": "{{ g.user.id }}", "wallet_name": &lt;string&gt;, "user_name": &lt;string&gt;}' -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" -H "Content-type: application/json"
<code
>curl -X POST {{ request.url_root }}usermanager/api/v1/users -d
'{"admin_id": "{{ g.user.id }}", "wallet_name": &lt;string&gt;,
"user_name": &lt;string&gt;}' -H "X-Api-Key: {{
g.user.wallets[0].inkey }}" -H "Content-type: application/json"
</code>
</q-card-section>
</q-card>
@ -81,41 +138,78 @@
<q-expansion-item group="api" dense expand-separator label="POST wallet">
<q-card>
<q-card-section>
<code><span class="text-light-green">POST</span> /usermanager/api/v1/wallets</code>
<code
><span class="text-light-green">POST</span>
/usermanager/api/v1/wallets</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;, "Content-type": "application/json"}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json) - "admin_id" is a YOUR user ID</h5>
<code>{"user_id": &lt;string&gt;, "wallet_name": &lt;string&gt;, "admin_id": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"checking_id": &lt;string&gt;,"payment_request": &lt;string&gt;}</code>
<code
>{"X-Api-Key": &lt;string&gt;, "Content-type":
"application/json"}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Body (application/json) - "admin_id" is a YOUR user ID
</h5>
<code
>{"user_id": &lt;string&gt;, "wallet_name": &lt;string&gt;,
"admin_id": &lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"checking_id": &lt;string&gt;,"payment_request":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}usermanager/api/v1/wallets -d '{"user_id": &lt;string&gt;, "wallet_name": &lt;string&gt;, "admin_id": "{{ g.user.id }}"}' -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" -H "Content-type: application/json"
<code
>curl -X POST {{ request.url_root }}usermanager/api/v1/wallets -d
'{"user_id": &lt;string&gt;, "wallet_name": &lt;string&gt;,
"admin_id": "{{ g.user.id }}"}' -H "X-Api-Key: {{
g.user.wallets[0].inkey }}" -H "Content-type: application/json"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="DELETE user and their wallets">
<q-expansion-item
group="api"
dense
expand-separator
label="DELETE user and their wallets"
>
<q-card>
<q-card-section>
<code><span class="text-red">DELETE</span> /usermanager/api/v1/users/&lt;user_id&gt;</code>
<code
><span class="text-red">DELETE</span>
/usermanager/api/v1/users/&lt;user_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X DELETE {{ request.url_root }}usermanager/api/v1/users/&lt;user_id&gt; -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" </code>
<code
>curl -X DELETE {{ request.url_root
}}usermanager/api/v1/users/&lt;user_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="DELETE wallet">
<q-card>
<q-card-section>
<code><span class="text-red">DELETE</span> /usermanager/api/v1/wallets/&lt;wallet_id&gt;</code>
<code
><span class="text-red">DELETE</span>
/usermanager/api/v1/wallets/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X DELETE {{ request.url_root }}usermanager/api/v1/wallets/&lt;wallet_id&gt; -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" </code>
<code
>curl -X DELETE {{ request.url_root
}}usermanager/api/v1/wallets/&lt;wallet_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

377
lnbits/extensions/usermanager/templates/usermanager/index.html

@ -1,15 +1,14 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block page %}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="userDialog.show = true">New User</q-btn>
<q-btn unelevated color="deep-purple" @click="walletDialog.show = true">New Wallet
<q-btn unelevated color="deep-purple" @click="userDialog.show = true"
>New User</q-btn
>
<q-btn unelevated color="deep-purple" @click="walletDialog.show = true"
>New Wallet
</q-btn>
</q-card-section>
</q-card>
@ -21,22 +20,23 @@
<h5 class="text-subtitle1 q-my-none">Users</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportUsersCSV">Export to CSV</q-btn>
<q-btn flat color="grey" @click="exportUsersCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="users"
row-key="id"
:columns="usersTable.columns"
:pagination.sync="usersTable.pagination">
:pagination.sync="usersTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -44,16 +44,18 @@
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteUser(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteUser(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -69,23 +71,24 @@
<h5 class="text-subtitle1 q-my-none">Wallets</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportWalletsCSV">Export to CSV</q-btn>
<q-btn flat color="grey" @click="exportWalletsCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="wallets"
row-key="id"
:columns="walletsTable.columns"
:pagination.sync="walletsTable.pagination">
:pagination.sync="walletsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -94,23 +97,32 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="account_balance_wallet" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.walllink" target="_blank"></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="account_balance_wallet"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.walllink"
target="_blank"
></q-btn>
<q-tooltip>
Link to wallet
</q-tooltip>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteWallet(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteWallet(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -118,8 +130,6 @@
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
@ -136,62 +146,75 @@
</q-card>
</div>
<q-dialog v-model="userDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;">
<q-form @submit="sendUserFormData" class="q-gutter-md">
<q-input filled dense
<q-input
filled
dense
v-model.trim="userDialog.data.usrname"
label="Username"></q-input>
<q-input filled dense
label="Username"
></q-input>
<q-input
filled
dense
v-model.trim="userDialog.data.walname"
label="Initial wallet name"></q-input>
label="Initial wallet name"
></q-input>
<q-btn unelevated
<q-btn
unelevated
color="deep-purple"
:disable="userDialog.data.walname == null"
type="submit">Create User</q-btn>
type="submit"
>Create User</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="walletDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;">
<q-form @submit="sendWalletFormData" class="q-gutter-md">
<q-select filled dense emit-value v-model="walletDialog.data.user" :options="userOptions" label="User *">
<q-select
filled
dense
emit-value
v-model="walletDialog.data.user"
:options="userOptions"
label="User *"
>
</q-select>
<q-input filled dense
<q-input
filled
dense
v-model.trim="walletDialog.data.walname"
label="Wallet name"></q-input>
<q-btn unelevated
label="Wallet name"
></q-input>
<q-btn
unelevated
color="deep-purple"
:disable="walletDialog.data.walname == null"
type="submit">Create Wallet</q-btn>
type="submit"
>Create Wallet</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %}
{% block scripts %}
{{ window_vars(user) }}
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapUserManager = function (obj) {
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
obj.walllink = ['../wallet?usr=', obj.user, '&wal=', obj.id].join('');
obj._data = _.clone(obj);
return obj;
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.walllink = ['../wallet?usr=', obj.user, '&wal=', obj.id].join('')
obj._data = _.clone(obj)
return obj
}
new Vue({
@ -199,7 +222,6 @@
mixins: [windowMixin],
data: function () {
return {
wallets: [],
users: [],
@ -217,7 +239,12 @@
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
{name: 'user', align: 'left', label: 'User', field: 'user'},
{name: 'adminkey', align: 'left', label: 'Admin Key', field: 'adminkey'},
{
name: 'adminkey',
align: 'left',
label: 'Admin Key',
field: 'adminkey'
},
{name: 'inkey', align: 'left', label: 'Invoice Key', field: 'inkey'}
],
pagination: {
@ -231,8 +258,8 @@
userDialog: {
show: false,
data: {}
},
};
}
}
},
computed: {
userOptions: function () {
@ -241,176 +268,186 @@
return {
value: String(obj.id),
label: String(obj.id)
};
});
},
}
})
}
},
methods: {
///////////////Users////////////////////////////
getUsers: function () {
var self = this
var self = this;
LNbits.api.request(
LNbits.api
.request(
'GET',
'/usermanager/api/v1/users',
this.g.user.wallets[0].inkey
).then(function (response) {
)
.then(function (response) {
self.users = response.data.map(function (obj) {
return mapUserManager(obj);
});
});
return mapUserManager(obj)
})
})
},
openUserUpdateDialog: function (linkId) {
var link = _.findWhere(this.users, {id: linkId});
var link = _.findWhere(this.users, {id: linkId})
this.userDialog.data = _.clone(link._data);
this.userDialog.show = true;
this.userDialog.data = _.clone(link._data)
this.userDialog.show = true
},
sendUserFormData: function () {
if (this.userDialog.data.id){}
else{
if (this.userDialog.data.id) {
} else {
var data = {
admin_id: this.g.user.id,
user_name: this.userDialog.data.usrname,
wallet_name: this.userDialog.data.walname
};}
}
}
{ this.createUser(data); }
{
this.createUser(data)
}
},
createUser: function (data) {
var self = this;
LNbits.api.request(
var self = this
LNbits.api
.request(
'POST',
'/usermanager/api/v1/users',
this.g.user.wallets[0].inkey,
data
).then(function (response) {
self.users.push(mapUserManager(response.data));
self.userDialog.show = false;
self.userDialog.data = {};
data = {};
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
)
.then(function (response) {
self.users.push(mapUserManager(response.data))
self.userDialog.show = false
self.userDialog.data = {}
data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteUser: function (userId) {
var self = this;
var self = this
console.log(userId)
LNbits.utils.confirmDialog(
'Are you sure you want to delete this User link?'
).onOk(function () {
LNbits.api.request(
LNbits.utils
.confirmDialog('Are you sure you want to delete this User link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/usermanager/api/v1/users/' + userId,
self.g.user.wallets[0].inkey
).then(function (response) {
self.users = _.reject(self.users, function (obj) { return obj.id == userId; });
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
});
)
.then(function (response) {
self.users = _.reject(self.users, function (obj) {
return obj.id == userId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportUsersCSV: function () {
LNbits.utils.exportCSV(this.usersTable.columns, this.users);
LNbits.utils.exportCSV(this.usersTable.columns, this.users)
},
///////////////Wallets////////////////////////////
getWallets: function () {
var self = this;
var self = this
LNbits.api.request(
LNbits.api
.request(
'GET',
'/usermanager/api/v1/wallets',
this.g.user.wallets[0].inkey
).then(function (response) {
)
.then(function (response) {
self.wallets = response.data.map(function (obj) {
return mapUserManager(obj);
});
});
return mapUserManager(obj)
})
})
},
openWalletUpdateDialog: function (linkId) {
var link = _.findWhere(this.users, {id: linkId});
var link = _.findWhere(this.users, {id: linkId})
this.walletDialog.data = _.clone(link._data);
this.walletDialog.show = true;
this.walletDialog.data = _.clone(link._data)
this.walletDialog.show = true
},
sendWalletFormData: function () {
if (this.walletDialog.data.id){}
else{
if (this.walletDialog.data.id) {
} else {
var data = {
user_id: this.walletDialog.data.user,
admin_id: this.g.user.id,
wallet_name: this.walletDialog.data.walname
};}
}
}
{ this.createWallet(data); }
{
this.createWallet(data)
}
},
createWallet: function (data) {
var self = this;
LNbits.api.request(
var self = this
LNbits.api
.request(
'POST',
'/usermanager/api/v1/wallets',
this.g.user.wallets[0].inkey,
data
).then(function (response) {
self.wallets.push(mapUserManager(response.data));
self.walletDialog.show = false;
self.walletDialog.data = {};
data = {};
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
)
.then(function (response) {
self.wallets.push(mapUserManager(response.data))
self.walletDialog.show = false
self.walletDialog.data = {}
data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteWallet: function (userId) {
var self = this;
var self = this
LNbits.utils.confirmDialog(
'Are you sure you want to delete this wallet link?'
).onOk(function () {
LNbits.api.request(
LNbits.utils
.confirmDialog('Are you sure you want to delete this wallet link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/usermanager/api/v1/wallets/' + userId,
self.g.user.wallets[0].inkey
).then(function (response) {
self.wallets = _.reject(self.wallets, function (obj) { return obj.id == userId; });
}).catch(function (error) {
LNbits.utils.notifyApiError(error);
});
});
)
.then(function (response) {
self.wallets = _.reject(self.wallets, function (obj) {
return obj.id == userId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportWalletsCSV: function () {
LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets);
LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getUsers();
this.getWallets();
this.getUsers()
this.getWallets()
}
}
});
})
</script>
{% endblock %}

98
lnbits/extensions/withdraw/templates/withdraw/_api_docs.html

@ -4,64 +4,126 @@
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="List withdraw links">
<q-expansion-item
group="api"
dense
expand-separator
label="List withdraw links"
>
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /withdraw/api/v1/links</code>
<code
><span class="text-light-blue">GET</span> /withdraw/api/v1/links</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}withdraw/api/v1/links -H "X-Api-Key: &lt;invoice_key&gt;" </code>
<code
>curl -X GET {{ request.url_root }}withdraw/api/v1/links -H
"X-Api-Key: &lt;invoice_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create a withdraw link">
<q-expansion-item
group="api"
dense
expand-separator
label="Create a withdraw link"
>
<q-card>
<q-card-section>
<code><span class="text-light-green">POST</span> /withdraw/api/v1/links</code>
<code
><span class="text-light-green">POST</span>
/withdraw/api/v1/links</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;, "max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;, "wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code
>{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}withdraw/api/v1/links -d '{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;, "max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;, "wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H "Content-type: application/json" -H "X-Api-Key: &lt;admin_key&gt;"
<code
>curl -X POST {{ request.url_root }}withdraw/api/v1/links -d
'{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Update a withdraw link">
<q-expansion-item
group="api"
dense
expand-separator
label="Update a withdraw link"
>
<q-card>
<q-card-section>
<code><span class="text-light-blue">PUT</span> /withdraw/api/v1/links/&lt;withdraw_id&gt;</code>
<code
><span class="text-light-blue">PUT</span>
/withdraw/api/v1/links/&lt;withdraw_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;, "max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;, "wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code
>{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X PUT {{ request.url_root }}withdraw/api/v1/links/&lt;withdraw_id&gt; -d '{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;, "max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;, "wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H "Content-type: application/json" -H "X-Api-Key: &lt;admin_key&gt;"
<code
>curl -X PUT {{ request.url_root
}}withdraw/api/v1/links/&lt;withdraw_id&gt; -d '{"title":
&lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Delete a withdraw link" class="q-pb-md">
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a withdraw link"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code><span class="text-light-green">DELETE</span> /withdraw/api/v1/links/&lt;withdraw_id&gt;</code>
<code
><span class="text-light-green">DELETE</span>
/withdraw/api/v1/links/&lt;withdraw_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 NO_CONTENT</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X DELETE {{ request.url_root }}withdraw/api/v1/links/&lt;withdraw_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
<code
>curl -X DELETE {{ request.url_root
}}withdraw/api/v1/links/&lt;withdraw_id&gt; -H "X-Api-Key:
&lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>

31
lnbits/extensions/withdraw/templates/withdraw/_lnurl.html

@ -1,12 +1,29 @@
<q-expansion-item
group="extras"
icon="info"
label="Powered by LNURL">
<q-expansion-item group="extras" icon="info" label="Powered by LNURL">
<q-card>
<q-card-section>
<p><b>WARNING: LNURL must be used over https or TOR</b><br/> LNURL is a range of lightning-network standards that allow us to use lightning-network differently. An LNURL withdraw is the permission for someone to pull a certain amount of funds from a lightning wallet. In this extension time is also added - an amount can be withdraw over a period of time. A typical use case for an LNURL withdraw is a faucet, although it is a very powerful technology, with much further reaching implications. For example, an LNURL withdraw could be minted to pay for a subscription service.</p>
<p>Exploring LNURL and finding use cases, is really helping inform lightning protocol development, rather than the protocol dictating how lightning-network should be engaged with.</p>
<small>Check <a href="https://github.com/fiatjaf/awesome-lnurl" target="_blank">Awesome LNURL</a> for further information.</small>
<p>
<b>WARNING: LNURL must be used over https or TOR</b><br />
LNURL is a range of lightning-network standards that allow us to use
lightning-network differently. An LNURL withdraw is the permission for
someone to pull a certain amount of funds from a lightning wallet. In
this extension time is also added - an amount can be withdraw over a
period of time. A typical use case for an LNURL withdraw is a faucet,
although it is a very powerful technology, with much further reaching
implications. For example, an LNURL withdraw could be minted to pay for
a subscription service.
</p>
<p>
Exploring LNURL and finding use cases, is really helping inform
lightning protocol development, rather than the protocol dictating how
lightning-network should be engaged with.
</p>
<small
>Check
<a href="https://github.com/fiatjaf/awesome-lnurl" target="_blank"
>Awesome LNURL</a
>
for further information.</small
>
</q-card-section>
</q-card>
</q-expansion-item>

31
lnbits/extensions/withdraw/templates/withdraw/display.html

@ -1,7 +1,4 @@
{% extends "public.html" %}
{% block page %}
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
@ -12,12 +9,18 @@
{% endif %}
<a href="lightning:{{ link.lnurl }}">
<q-responsive :ratio="1" class="q-mx-md">
<qrcode value="{{ link.lnurl }}" :options="{width: 800}" class="rounded-borders"></qrcode>
<qrcode
value="{{ link.lnurl }}"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')">Copy LNURL</q-btn>
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')"
>Copy LNURL</q-btn
>
</div>
</q-card-section>
</q-card>
@ -25,8 +28,12 @@
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-withdraw link</h6>
<p class="q-my-none">Use a LNURL compatible bitcoin wallet to claim the sats.</p>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">
LNbits LNURL-withdraw link
</h6>
<p class="q-my-none">
Use a LNURL compatible bitcoin wallet to claim the sats.
</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@ -37,16 +44,14 @@
</q-card>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode);
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin]
});
})
</script>
{% endblock %}

220
lnbits/extensions/withdraw/templates/withdraw/index.html

@ -1,23 +1,17 @@
{% extends "base.html" %}
{% from "macros.jinja" import window_vars with context %}
{% block scripts %}
{{ window_vars(user) }}
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block scripts %} {{ window_vars(user) }}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
{% assets filters='rjsmin', output='__bundle__/withdraw/index.js',
'withdraw/js/index.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
{% endblock %}
{% block page %}
{% endassets %} {% endblock %} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true">New withdraw link</q-btn>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
>New withdraw link</q-btn
>
</q-card-section>
</q-card>
@ -31,20 +25,19 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat
<q-table
dense
flat
:data="sortedWithdrawLinks"
row-key="id"
:columns="withdrawLinksTable.columns"
:pagination.sync="withdrawLinksTable.pagination">
:pagination.sync="withdrawLinksTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -53,19 +46,45 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.withdraw_url" target="_blank"></q-btn>
<q-btn unelevated dense size="xs" icon="visibility" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" @click="openQrCodeDialog(props.row.id)"></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.withdraw_url"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
></q-btn>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="openUpdateDialog(props.row.id)" icon="edit" color="light-blue"></q-btn>
<q-btn flat dense size="xs" @click="deleteWithdrawLink(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteWithdrawLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -78,7 +97,9 @@
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">LNbits LNURL-withdraw extension</h6>
<h6 class="text-subtitle1 q-my-none">
LNbits LNURL-withdraw extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@ -94,53 +115,97 @@
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md">
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input filled dense
<q-input
filled
dense
v-model.trim="formDialog.data.title"
type="text"
label="Link title *"></q-input>
<q-input filled dense
label="Link title *"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.min_withdrawable"
type="number"
label="Min withdrawable (sat) *"></q-input>
<q-input filled dense
label="Min withdrawable (sat) *"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.max_withdrawable"
type="number"
label="Max withdrawable (sat) *"></q-input>
<q-input filled dense
label="Max withdrawable (sat) *"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.uses"
type="number"
:default="1"
label="Amount of uses *"></q-input>
label="Amount of uses *"
></q-input>
<div class="row q-col-gutter-none">
<div class="col-8">
<q-input filled dense
<q-input
filled
dense
v-model.number="formDialog.data.wait_time"
type="number"
:default="1"
label="Time between withdrawals *">
label="Time between withdrawals *"
>
</q-input>
</div>
<div class="col-4 q-pl-xs">
<q-select filled dense v-model="formDialog.secondMultiplier" :options="formDialog.secondMultiplierOptions">
<q-select
filled
dense
v-model="formDialog.secondMultiplier"
:options="formDialog.secondMultiplierOptions"
>
</q-select>
</div>
</div>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox v-model="formDialog.data.is_unique" color="deep-purple"></q-checkbox>
<q-checkbox
v-model="formDialog.data.is_unique"
color="deep-purple"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Use unique withdraw QR codes to reduce `assmilking`</q-item-label>
<q-item-label caption>This is recommended if you are sharing the links on social media. NOT if you plan to print QR codes.</q-item-label>
<q-item-label
>Use unique withdraw QR codes to reduce
`assmilking`</q-item-label
>
<q-item-label caption
>This is recommended if you are sharing the links on social
media. NOT if you plan to print QR codes.</q-item-label
>
</q-item-section>
</q-item>
</q-list>
<div class="row q-mt-lg">
<q-btn v-if="formDialog.data.id" unelevated color="deep-purple" type="submit">Update withdraw link</q-btn>
<q-btn v-else unelevated
<q-btn
v-if="formDialog.data.id"
unelevated
color="deep-purple"
type="submit"
>Update withdraw link</q-btn
>
<q-btn
v-else
unelevated
color="deep-purple"
:disable="
formDialog.data.wallet == null ||
@ -153,8 +218,12 @@
) ||
formDialog.data.uses == null ||
formDialog.data.wait_time == null"
type="submit">Create withdraw link</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
type="submit"
>Create withdraw link</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
@ -164,20 +233,55 @@
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
{% raw %}
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="qrCodeDialog.data.lnurl" :options="{width: 800}" class="rounded-borders"></qrcode>
<qrcode
:value="qrCodeDialog.data.lnurl"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<p style="word-break: break-all">
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br>
<strong>Unique:</strong> {{ qrCodeDialog.data.is_unique }}<span v-if="qrCodeDialog.data.is_unique" class="text-deep-purple"> (QR code will change after each withdrawal)</span><br>
<strong>Max. withdrawable:</strong> {{ qrCodeDialog.data.max_withdrawable }} sat<br>
<strong>Wait time:</strong> {{ qrCodeDialog.data.wait_time }} seconds<br>
<strong>Withdraws:</strong> {{ qrCodeDialog.data.used }} / {{ qrCodeDialog.data.uses }} <q-linear-progress :value="qrCodeDialog.data.used / qrCodeDialog.data.uses" color="deep-purple" class="q-mt-sm"></q-linear-progress>
<p style="word-break: break-all;">
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
<strong>Unique:</strong> {{ qrCodeDialog.data.is_unique }}<span
v-if="qrCodeDialog.data.is_unique"
class="text-deep-purple"
>
(QR code will change after each withdrawal)</span
><br />
<strong>Max. withdrawable:</strong> {{
qrCodeDialog.data.max_withdrawable }} sat<br />
<strong>Wait time:</strong> {{ qrCodeDialog.data.wait_time }} seconds<br />
<strong>Withdraws:</strong> {{ qrCodeDialog.data.used }} / {{
qrCodeDialog.data.uses }}
<q-linear-progress
:value="qrCodeDialog.data.used / qrCodeDialog.data.uses"
color="deep-purple"
class="q-mt-sm"
></q-linear-progress>
</p>
{% endraw %}
<div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')" class="q-ml-sm">Copy LNURL</q-btn>
<q-btn outline color="grey" @click="copyText(qrCodeDialog.data.withdraw_url, 'Link copied to clipboard!')">Shareable link</q-btn>
<q-btn v-if="!qrCodeDialog.data.is_unique" outline color="grey" icon="print" type="a" :href="qrCodeDialog.data.print_url" target="_blank"></q-btn>
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
class="q-ml-sm"
>Copy LNURL</q-btn
>
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.withdraw_url, 'Link copied to clipboard!')"
>Shareable link</q-btn
>
<q-btn
v-if="!qrCodeDialog.data.is_unique"
outline
color="grey"
icon="print"
type="a"
:href="qrCodeDialog.data.print_url"
target="_blank"
></q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>

29
lnbits/extensions/withdraw/templates/withdraw/print_qr.html

@ -1,26 +1,21 @@
{% extends "print.html" %}
{% block page %}
{% extends "print.html" %} {% block page %}
<div class="row justify-center">
<div class="col-12 col-sm-8 col-lg-6 text-center">
{% for i in range(link.uses) %}
<div class="zimbabwe">
<div class="qr">
<qrcode value="{{ link.lnurl }}" :options="{width: 150}"></qrcode>
<br><br>
<strong>{{ SITE_TITLE }}</strong><br>
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br>
<small>Scan and follow link<br>or use Lightning wallet</small>
<br /><br />
<strong>{{ SITE_TITLE }}</strong><br />
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br />
<small>Scan and follow link<br />or use Lightning wallet</small>
</div>
<img src="{{ url_for('static', filename='images/note.jpg') }}">
<img src="{{ url_for('static', filename='images/note.jpg') }}" />
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block styles %}
{% endblock %} {% block styles %}
<style>
.zimbabwe {
page-break-inside: avoid;
@ -49,18 +44,16 @@
line-height: 1.1;
}
</style>
{% endblock %}
{% block scripts %}
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode);
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
created: function () {
window.print();
window.print()
}
});
})
</script>
{% endblock %}

230
lnbits/static/js/base.js

@ -1,6 +1,6 @@
var LOCALE = 'en';
var LOCALE = 'en'
var EventHub = new Vue();
var EventHub = new Vue()
var LNbits = {
api: {
@ -12,78 +12,97 @@ var LNbits = {
'X-Api-Key': apiKey
},
data: data
});
})
},
createInvoice: function (wallet, amount, memo) {
return this.request('post', '/api/v1/payments', wallet.inkey, {
out: false,
amount: amount,
memo: memo
});
})
},
payInvoice: function (wallet, bolt11) {
return this.request('post', '/api/v1/payments', wallet.adminkey, {
out: true,
bolt11: bolt11
});
})
},
getPayments: function (wallet, checkPending) {
var query_param = (checkPending) ? '?check_pending' : '';
return this.request('get', ['/api/v1/payments', query_param].join(''), wallet.inkey);
var query_param = checkPending ? '?check_pending' : ''
return this.request(
'get',
['/api/v1/payments', query_param].join(''),
wallet.inkey
)
},
getPayment: function (wallet, payhash) {
return this.request('get', '/api/v1/payments/' + payhash, wallet.inkey);
return this.request('get', '/api/v1/payments/' + payhash, wallet.inkey)
}
},
href: {
createWallet: function (walletName, userId) {
window.location.href = '/wallet?' + (userId ? 'usr=' + userId + '&' : '') + 'nme=' + walletName;
window.location.href =
'/wallet?' + (userId ? 'usr=' + userId + '&' : '') + 'nme=' + walletName
},
deleteWallet: function (walletId, userId) {
window.location.href = '/deletewallet?usr=' + userId + '&wal=' + walletId;
window.location.href = '/deletewallet?usr=' + userId + '&wal=' + walletId
}
},
map: {
extension: function (data) {
var obj = _.object(['code', 'isValid', 'name', 'shortDescription', 'icon'], data);
obj.url = ['/', obj.code, '/'].join('');
return obj;
var obj = _.object(
['code', 'isValid', 'name', 'shortDescription', 'icon'],
data
)
obj.url = ['/', obj.code, '/'].join('')
return obj
},
user: function (data) {
var obj = _.object(['id', 'email', 'extensions', 'wallets'], data);
var mapWallet = this.wallet;
obj.wallets = obj.wallets.map(function (obj) {
return mapWallet(obj);
}).sort(function (a, b) {
return a.name.localeCompare(b.name);
});
var obj = _.object(['id', 'email', 'extensions', 'wallets'], data)
var mapWallet = this.wallet
obj.wallets = obj.wallets
.map(function (obj) {
return mapWallet(obj)
})
.sort(function (a, b) {
return a.name.localeCompare(b.name)
})
obj.walletOptions = obj.wallets.map(function (obj) {
return {
label: [obj.name, ' - ', obj.id].join(''),
value: obj.id
};
});
return obj;
}
})
return obj
},
wallet: function (data) {
var obj = _.object(['id', 'name', 'user', 'adminkey', 'inkey', 'balance'], data);
obj.msat = obj.balance;
obj.sat = Math.round(obj.balance / 1000);
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat);
obj.url = ['/wallet?usr=', obj.user, '&wal=', obj.id].join('');
return obj;
var obj = _.object(
['id', 'name', 'user', 'adminkey', 'inkey', 'balance'],
data
)
obj.msat = obj.balance
obj.sat = Math.round(obj.balance / 1000)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat)
obj.url = ['/wallet?usr=', obj.user, '&wal=', obj.id].join('')
return obj
},
payment: function (data) {
var obj = _.object(['payhash', 'pending', 'amount', 'fee', 'memo', 'time'], data);
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
obj.msat = obj.amount;
obj.sat = obj.msat / 1000;
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat);
obj.isIn = obj.amount > 0;
obj.isOut = obj.amount < 0;
obj.isPaid = obj.pending == 0;
obj._q = [obj.memo, obj.sat].join(' ').toLowerCase();
return obj;
var obj = _.object(
['payhash', 'pending', 'amount', 'fee', 'memo', 'time'],
data
)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.msat = obj.amount
obj.sat = obj.msat / 1000
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat)
obj.isIn = obj.amount > 0
obj.isOut = obj.amount < 0
obj.isPaid = obj.pending == 0
obj._q = [obj.memo, obj.sat].join(' ').toLowerCase()
return obj
}
},
utils: {
@ -98,84 +117,99 @@ var LNbits = {
flat: true,
color: 'grey'
}
});
})
},
formatCurrency: function (value, currency) {
return new Intl.NumberFormat(LOCALE, {style: 'currency', currency: currency}).format(value);
return new Intl.NumberFormat(LOCALE, {
style: 'currency',
currency: currency
}).format(value)
},
formatSat: function (value) {
return new Intl.NumberFormat(LOCALE).format(value);
return new Intl.NumberFormat(LOCALE).format(value)
},
notifyApiError: function (error) {
var types = {
400: 'warning',
401: 'warning',
500: 'negative'
};
}
Quasar.plugins.Notify.create({
timeout: 5000,
type: types[error.response.status] || 'warning',
message: error.response.data.message || null,
caption: [error.response.status, ' ', error.response.statusText].join('').toUpperCase() || null,
caption:
[error.response.status, ' ', error.response.statusText]
.join('')
.toUpperCase() || null,
icon: null
});
})
},
search: function (data, q, field, separator) {
var field = field || '_q';
var field = field || '_q'
try {
var queries = q.toLowerCase().split(separator || ' ');
var queries = q.toLowerCase().split(separator || ' ')
return data.filter(function (obj) {
var matches = 0;
var matches = 0
_.each(queries, function (q) {
if (obj[field].indexOf(q) !== -1) matches++;
});
return matches == queries.length;
});
if (obj[field].indexOf(q) !== -1) matches++
})
return matches == queries.length
})
} catch (err) {
return data;
return data
}
},
exportCSV: function (columns, data) {
var wrapCsvValue = function (val, formatFn) {
var formatted = formatFn !== void 0
? formatFn(val)
: val;
var formatted = formatFn !== void 0 ? formatFn(val) : val
formatted = (formatted === void 0 || formatted === null)
? ''
: String(formatted);
formatted =
formatted === void 0 || formatted === null ? '' : String(formatted)
formatted = formatted.split('"').join('""');
formatted = formatted.split('"').join('""')
return `"${formatted}"`;
return `"${formatted}"`
}
var content = [columns.map(function (col) {
return wrapCsvValue(col.label);
})].concat(data.map(function (row) {
return columns.map(function (col) {
var content = [
columns.map(function (col) {
return wrapCsvValue(col.label)
})
]
.concat(
data.map(function (row) {
return columns
.map(function (col) {
return wrapCsvValue(
(typeof col.field === 'function')
typeof col.field === 'function'
? col.field(row)
: row[(col.field === void 0) ? col.name : col.field],
: row[col.field === void 0 ? col.name : col.field],
col.format
);
}).join(',');
})).join('\r\n');
)
})
.join(',')
})
)
.join('\r\n')
var status = Quasar.utils.exportFile('table-export.csv', content, 'text/csv');
var status = Quasar.utils.exportFile(
'table-export.csv',
content,
'text/csv'
)
if (status !== true) {
Quasar.plugins.Notify.create({
message: 'Browser denied file download...',
color: 'negative',
icon: null
});
})
}
}
}
}
};
var windowMixin = {
data: function () {
@ -185,44 +219,52 @@ var windowMixin = {
extensions: [],
user: null,
wallet: null,
payments: [],
payments: []
}
}
};
},
methods: {
toggleDarkMode: function () {
this.$q.dark.toggle();
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive);
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
},
copyText: function (text, message, position) {
var notify = this.$q.notify;
var notify = this.$q.notify
Quasar.utils.copyToClipboard(text).then(function () {
notify({message: message || 'Copied to clipboard!', position: position || 'bottom'});
});
notify({
message: message || 'Copied to clipboard!',
position: position || 'bottom'
})
})
}
},
created: function () {
this.$q.dark.set(this.$q.localStorage.getItem('lnbits.darkMode'));
this.$q.dark.set(this.$q.localStorage.getItem('lnbits.darkMode'))
if (window.user) {
this.g.user = Object.freeze(LNbits.map.user(window.user));
this.g.user = Object.freeze(LNbits.map.user(window.user))
}
if (window.wallet) {
this.g.wallet = Object.freeze(LNbits.map.wallet(window.wallet));
this.g.wallet = Object.freeze(LNbits.map.wallet(window.wallet))
}
if (window.extensions) {
var user = this.g.user;
this.g.extensions = Object.freeze(window.extensions.map(function (data) {
return LNbits.map.extension(data);
}).map(function (obj) {
var user = this.g.user
this.g.extensions = Object.freeze(
window.extensions
.map(function (data) {
return LNbits.map.extension(data)
})
.map(function (obj) {
if (user) {
obj.isEnabled = user.extensions.indexOf(obj.code) != -1;
obj.isEnabled = user.extensions.indexOf(obj.code) != -1
} else {
obj.isEnabled = false;
obj.isEnabled = false
}
return obj
})
.sort(function (a, b) {
return a.name > b.name
})
)
}
return obj;
}).sort(function (a, b) {
return a.name > b.name;
}));
}
}
};

63
lnbits/static/js/components.js

@ -8,10 +8,10 @@ Vue.component('lnbits-fsat', {
template: '<span>{{ fsat }}</span>',
computed: {
fsat: function () {
return LNbits.utils.formatSat(this.amount);
return LNbits.utils.formatSat(this.amount)
}
}
});
})
Vue.component('lnbits-wallet-list', {
data: function () {
@ -70,33 +70,34 @@ Vue.component('lnbits-wallet-list', {
`,
computed: {
wallets: function () {
var bal = this.activeBalance;
var bal = this.activeBalance
return this.user.wallets.map(function (obj) {
obj.live_fsat = (bal.length && bal[0] == obj.id)
obj.live_fsat =
bal.length && bal[0] == obj.id
? LNbits.utils.formatSat(bal[1])
: obj.fsat;
return obj;
});
: obj.fsat
return obj
})
}
},
methods: {
createWallet: function () {
LNbits.href.createWallet(this.walletName, this.user.id);
LNbits.href.createWallet(this.walletName, this.user.id)
},
updateWalletBalance: function (payload) {
this.activeBalance = payload;
this.activeBalance = payload
}
},
created: function () {
if (window.user) {
this.user = LNbits.map.user(window.user);
this.user = LNbits.map.user(window.user)
}
if (window.wallet) {
this.activeWallet = LNbits.map.wallet(window.wallet);
this.activeWallet = LNbits.map.wallet(window.wallet)
}
EventHub.$on('update-wallet-balance', this.updateWalletBalance);
EventHub.$on('update-wallet-balance', this.updateWalletBalance)
}
});
})
Vue.component('lnbits-extension-list', {
data: function () {
@ -140,30 +141,34 @@ Vue.component('lnbits-extension-list', {
`,
computed: {
userExtensions: function () {
if (!this.user) return [];
if (!this.user) return []
var path = window.location.pathname;
var userExtensions = this.user.extensions;
var path = window.location.pathname
var userExtensions = this.user.extensions
return this.extensions.filter(function (obj) {
return userExtensions.indexOf(obj.code) !== -1;
}).map(function (obj) {
obj.isActive = path.startsWith(obj.url);
return obj;
});
return this.extensions
.filter(function (obj) {
return userExtensions.indexOf(obj.code) !== -1
})
.map(function (obj) {
obj.isActive = path.startsWith(obj.url)
return obj
})
}
},
created: function () {
if (window.extensions) {
this.extensions = window.extensions.map(function (data) {
return LNbits.map.extension(data);
}).sort(function (a, b) {
return a.name.localeCompare(b.name);
});
this.extensions = window.extensions
.map(function (data) {
return LNbits.map.extension(data)
})
.sort(function (a, b) {
return a.name.localeCompare(b.name)
})
}
if (window.user) {
this.user = LNbits.map.user(window.user);
this.user = LNbits.map.user(window.user)
}
}
});
})

5
package.json

@ -0,0 +1,5 @@
{
"devDependencies": {
"prettier": "^2.0.5"
}
}
Loading…
Cancel
Save