mirror of https://github.com/lukechilds/lnbits.git
Eneko Illarramendi
5 years ago
committed by
GitHub
33 changed files with 5068 additions and 3951 deletions
@ -0,0 +1,12 @@ |
|||
{ |
|||
"semi": false, |
|||
"arrowParens": "avoid", |
|||
"insertPragma": false, |
|||
"printWidth": 80, |
|||
"proseWrap": "preserve", |
|||
"singleQuote": true, |
|||
"trailingComma": "none", |
|||
"useTabs": false, |
|||
"jsxBracketSameLine": false, |
|||
"bracketSpacing": false |
|||
} |
@ -0,0 +1,2 @@ |
|||
prettier: |
|||
./node_modules/.bin/prettier --write lnbits/static/js/** lnbits/core/static/js/** lnbits/extensions/*/templates/** |
@ -1,4 +1,4 @@ |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin] |
|||
}); |
|||
}) |
|||
|
@ -1,16 +1,24 @@ |
|||
|
|||
<q-expansion-item |
|||
group="extras" |
|||
icon="swap_vertical_circle" |
|||
label="Info" |
|||
:content-inset-level="0.5" |
|||
> |
|||
<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> |
|||
</q-card-section> |
|||
|
|||
</q-card-section></q-expansion-item> |
|||
<q-expansion-item |
|||
group="extras" |
|||
icon="swap_vertical_circle" |
|||
label="Info" |
|||
:content-inset-level="0.5" |
|||
> |
|||
<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-section> |
|||
</q-card> |
|||
</q-expansion-item> |
|||
|
@ -1,230 +1,252 @@ |
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">AMilks</h5> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">AMilks</h5> |
|||
</div> |
|||
<q-table dense flat |
|||
:data="amilks" |
|||
row-key="id" |
|||
:columns="amilksTable.columns" |
|||
: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" |
|||
> |
|||
{{ 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" |
|||
|
|||
> |
|||
{{ 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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<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> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "amilk/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<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> |
|||
<q-input filled dense |
|||
v-model.trim="amilkDialog.data.lnurl" |
|||
type="url" |
|||
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 |
|||
v-model.trim="amilkDialog.data.atime" |
|||
type="number" |
|||
label="Hit frequency (secs)" |
|||
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> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
</div> |
|||
<q-table |
|||
dense |
|||
flat |
|||
:data="amilks" |
|||
row-key="id" |
|||
:columns="amilksTable.columns" |
|||
: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"> |
|||
{{ 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"> |
|||
{{ 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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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; |
|||
} |
|||
<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> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "amilk/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
amilks: [], |
|||
amilksTable: { |
|||
columns: [ |
|||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
|||
{name: 'lnurl', align: 'left', label: 'LNURL', field: 'lnurl'}, |
|||
{name: 'atime', align: 'left', label: 'Freq', field: 'atime'}, |
|||
{name: 'amount', align: 'left', label: 'Amount', field: 'amount'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
amilkDialog: { |
|||
show: false, |
|||
data: {} |
|||
<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> |
|||
<q-input |
|||
filled |
|||
dense |
|||
v-model.trim="amilkDialog.data.lnurl" |
|||
type="url" |
|||
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 |
|||
v-model.trim="amilkDialog.data.atime" |
|||
type="number" |
|||
label="Hit frequency (secs)" |
|||
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 |
|||
> |
|||
<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) }} |
|||
<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 |
|||
} |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
amilks: [], |
|||
amilksTable: { |
|||
columns: [ |
|||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
|||
{name: 'lnurl', align: 'left', label: 'LNURL', field: 'lnurl'}, |
|||
{name: 'atime', align: 'left', label: 'Freq', field: 'atime'}, |
|||
{name: 'amount', align: 'left', label: 'Amount', field: 'amount'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}; |
|||
}, |
|||
methods: { |
|||
|
|||
|
|||
|
|||
getAMilks: function () { |
|||
var self = this; |
|||
}, |
|||
amilkDialog: { |
|||
show: false, |
|||
data: {} |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
getAMilks: function () { |
|||
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); |
|||
function MILK(item){ |
|||
|
|||
window.setInterval(function(){ |
|||
|
|||
|
|||
LNbits.api.request( |
|||
'GET', |
|||
'/amilk/api/v1/amilk/milk/' + item.id, |
|||
"Lorem" |
|||
).then(function (response) { |
|||
self.amilks = response.data.map(function (obj) { |
|||
return mapAMilk(obj); |
|||
}); |
|||
}); |
|||
}, |
|||
item.atime*1000); |
|||
|
|||
response.data.forEach(MILK) |
|||
function MILK(item) { |
|||
window.setInterval(function () { |
|||
LNbits.api |
|||
.request( |
|||
'GET', |
|||
'/amilk/api/v1/amilk/milk/' + item.id, |
|||
'Lorem' |
|||
) |
|||
.then(function (response) { |
|||
self.amilks = response.data.map(function (obj) { |
|||
return mapAMilk(obj) |
|||
}) |
|||
}) |
|||
}, item.atime * 1000) |
|||
} |
|||
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; |
|||
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 |
|||
|
|||
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); |
|||
}); |
|||
}, |
|||
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( |
|||
'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); |
|||
}); |
|||
}); |
|||
}, |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.amilksTable.columns, this.amilks); |
|||
} |
|||
) |
|||
.then(function (response) { |
|||
self.amilks.push(mapAMilk(response.data)) |
|||
self.amilkDialog.show = false |
|||
self.amilkDialog.data = {} |
|||
}) |
|||
.catch(function (error) { |
|||
LNbits.utils.notifyApiError(error) |
|||
}) |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getAMilks(); |
|||
} |
|||
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( |
|||
'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) |
|||
}) |
|||
}) |
|||
}, |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.amilksTable.columns, this.amilks) |
|||
} |
|||
}); |
|||
</script> |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getAMilks() |
|||
} |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
File diff suppressed because it is too large
@ -1 +1,3 @@ |
|||
<script>console.log("{{ stall }}")</script> |
|||
<script> |
|||
console.log('{{ stall }}') |
|||
</script> |
|||
|
@ -1,54 +1,57 @@ |
|||
{% 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-section> |
|||
<q-item-label>{{ tool.name }}</q-item-label> |
|||
<q-item-label caption>{{ tool.language }}</q-item-label> |
|||
</q-item-section> |
|||
{% endraw %} |
|||
</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> |
|||
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code> |
|||
</q-card-section> |
|||
</q-card> |
|||
{% endblock %} |
|||
|
|||
{% block scripts %} |
|||
{{ window_vars(user) }} |
|||
<script> |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
tools: [] |
|||
}; |
|||
}, |
|||
created: function () { |
|||
var self = this; |
|||
|
|||
// axios is available for making requests |
|||
axios({ |
|||
method: 'GET', |
|||
url: '/example/api/v1/tools', |
|||
headers: { |
|||
'X-example-header': 'not-used' |
|||
} |
|||
}).then(function (response) { |
|||
self.tools = response.data; |
|||
}); |
|||
{% 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-section> |
|||
<q-item-label>{{ tool.name }}</q-item-label> |
|||
<q-item-label caption>{{ tool.language }}</q-item-label> |
|||
</q-item-section> |
|||
{% endraw %} |
|||
</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> |
|||
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code> |
|||
</q-card-section> |
|||
</q-card> |
|||
{% endblock %} {% block scripts %} {{ window_vars(user) }} |
|||
<script> |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
tools: [] |
|||
} |
|||
}); |
|||
</script> |
|||
}, |
|||
created: function () { |
|||
var self = this |
|||
|
|||
// axios is available for making requests |
|||
axios({ |
|||
method: 'GET', |
|||
url: '/example/api/v1/tools', |
|||
headers: { |
|||
'X-example-header': 'not-used' |
|||
} |
|||
}).then(function (response) { |
|||
self.tools = response.data |
|||
}) |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -1,107 +1,122 @@ |
|||
{% 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> |
|||
<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> |
|||
</q-responsive> |
|||
</a> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn outline color="grey" @click="copyText(paymentReq)">Copy invoice</q-btn> |
|||
</div> |
|||
{% 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 |
|||
> |
|||
<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> |
|||
</q-responsive> |
|||
</a> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn outline color="grey" @click="copyText(paymentReq)" |
|||
>Copy invoice</q-btn |
|||
> |
|||
</div> |
|||
<div v-if="redirectUrl"> |
|||
<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> |
|||
</div> |
|||
</div> |
|||
<div v-if="redirectUrl"> |
|||
<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 |
|||
> |
|||
</div> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
<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 paywall</h6> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
</div> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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); |
|||
<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 paywall</h6> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
</div> |
|||
{% 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) |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
paymentReq: null, |
|||
redirectUrl: null |
|||
}; |
|||
}, |
|||
methods: { |
|||
getInvoice: function () { |
|||
var self = this; |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
paymentReq: null, |
|||
redirectUrl: null |
|||
} |
|||
}, |
|||
methods: { |
|||
getInvoice: function () { |
|||
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( |
|||
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice', |
|||
{checking_id: response.data.checking_id} |
|||
).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); |
|||
axios |
|||
.post( |
|||
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice', |
|||
{checking_id: response.data.checking_id} |
|||
) |
|||
.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 |
|||
) |
|||
|
|||
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); |
|||
}); |
|||
} |
|||
}, |
|||
created: function () { |
|||
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}'); |
|||
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) |
|||
}) |
|||
} |
|||
}, |
|||
created: function () { |
|||
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}') |
|||
|
|||
if (url) { |
|||
this.redirectUrl = url; |
|||
} else { |
|||
this.getInvoice(); |
|||
}; |
|||
if (url) { |
|||
this.redirectUrl = url |
|||
} else { |
|||
this.getInvoice() |
|||
} |
|||
}); |
|||
</script> |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -1,214 +1,274 @@ |
|||
{% extends "base.html" %} |
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">Paywalls</h5> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">Paywalls</h5> |
|||
</div> |
|||
<q-table dense flat |
|||
:data="paywalls" |
|||
row-key="id" |
|||
:columns="paywallsTable.columns" |
|||
: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" |
|||
> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
<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-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<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 paywall extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "paywall/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<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> |
|||
<q-input filled dense |
|||
v-model.trim="formDialog.data.url" |
|||
type="url" |
|||
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 |
|||
v-model.trim="formDialog.data.memo" |
|||
label="Memo" |
|||
placeholder="LNbits invoice"></q-input> |
|||
<div class="row q-mt-lg"> |
|||
<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> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
</div> |
|||
<q-table |
|||
dense |
|||
flat |
|||
:data="paywalls" |
|||
row-key="id" |
|||
:columns="paywallsTable.columns" |
|||
: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"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
<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-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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; |
|||
} |
|||
<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 paywall extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "paywall/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<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> |
|||
<q-input |
|||
filled |
|||
dense |
|||
v-model.trim="formDialog.data.url" |
|||
type="url" |
|||
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 |
|||
v-model.trim="formDialog.data.memo" |
|||
label="Memo" |
|||
placeholder="LNbits invoice" |
|||
></q-input> |
|||
<div class="row q-mt-lg"> |
|||
<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 |
|||
> |
|||
</div> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
</div> |
|||
{% 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 |
|||
} |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
paywalls: [], |
|||
paywallsTable: { |
|||
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, |
|||
sort: function (a, b, rowA, rowB) { |
|||
return rowA.amount - rowB.amount; |
|||
} |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
paywalls: [], |
|||
paywallsTable: { |
|||
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, |
|||
sort: function (a, b, rowA, rowB) { |
|||
return rowA.amount - rowB.amount |
|||
} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
formDialog: { |
|||
show: false, |
|||
data: {} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}; |
|||
}, |
|||
methods: { |
|||
getPaywalls: function () { |
|||
var self = this; |
|||
}, |
|||
formDialog: { |
|||
show: false, |
|||
data: {} |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
getPaywalls: function () { |
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
createPaywall: function () { |
|||
var data = { |
|||
url: this.formDialog.data.url, |
|||
memo: this.formDialog.data.memo, |
|||
amount: this.formDialog.data.amount |
|||
}; |
|||
var self = this; |
|||
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 |
|||
|
|||
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); |
|||
}); |
|||
}, |
|||
deletePaywall: function (paywallId) { |
|||
var self = this; |
|||
var paywall = _.findWhere(this.paywalls, {id: paywallId}); |
|||
) |
|||
.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}) |
|||
|
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls); |
|||
} |
|||
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) |
|||
}) |
|||
}) |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getPaywalls(); |
|||
} |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls) |
|||
} |
|||
}); |
|||
</script> |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getPaywalls() |
|||
} |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -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> |
|||
|
@ -1,221 +1,423 @@ |
|||
{% extends "base.html" %} |
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
{% 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-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">TPoS</h5> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">TPoS</h5> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
|||
</div> |
|||
<q-table dense flat |
|||
:data="tposs" |
|||
row-key="id" |
|||
:columns="tpossTable.columns" |
|||
: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" |
|||
> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
</div> |
|||
<q-table |
|||
dense |
|||
flat |
|||
:data="tposs" |
|||
row-key="id" |
|||
:columns="tpossTable.columns" |
|||
: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"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
|
|||
<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-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
<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-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
<div class="col-12 col-md-5 q-gutter-y-md"> |
|||
<q-card> |
|||
<q-card-section> |
|||
<h6 class="text-subtitle1 q-my-none">LNbits TPoS extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<div class="col-12 col-md-5 q-gutter-y-md"> |
|||
<q-card> |
|||
<q-card-section> |
|||
<h6 class="text-subtitle1 q-my-none">LNbits TPoS extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "tpos/_api_docs.html" %} |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "tpos/_api_docs.html" %} |
|||
<q-separator></q-separator> |
|||
{% include "tpos/_tpos.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</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-form @submit="createTPoS" class="q-gutter-md"> |
|||
<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" |
|||
:options="g.user.walletOptions" |
|||
label="Wallet *"></q-select> |
|||
<q-select filled dense |
|||
emit-value v-model="formDialog.data.currency" |
|||
:options="currencyOptions" |
|||
label="Currency *"></q-select> |
|||
<div class="row q-mt-lg"> |
|||
<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> |
|||
</div> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
{% include "tpos/_tpos.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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; |
|||
} |
|||
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog"> |
|||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;"> |
|||
<q-form @submit="createTPoS" class="q-gutter-md"> |
|||
<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" |
|||
:options="g.user.walletOptions" |
|||
label="Wallet *" |
|||
></q-select> |
|||
<q-select |
|||
filled |
|||
dense |
|||
emit-value |
|||
v-model="formDialog.data.currency" |
|||
:options="currencyOptions" |
|||
label="Currency *" |
|||
></q-select> |
|||
<div class="row q-mt-lg"> |
|||
<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 |
|||
> |
|||
</div> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
</div> |
|||
{% 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 |
|||
} |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
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' |
|||
], |
|||
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'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
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' |
|||
], |
|||
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' |
|||
} |
|||
}, |
|||
formDialog: { |
|||
show: false, |
|||
data: {} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}; |
|||
}, |
|||
methods: { |
|||
closeFormDialog: function () { |
|||
this.formDialog.data = {}; |
|||
}, |
|||
getTPoSs: function () { |
|||
var self = this; |
|||
formDialog: { |
|||
show: false, |
|||
data: {} |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
closeFormDialog: function () { |
|||
this.formDialog.data = {} |
|||
}, |
|||
getTPoSs: function () { |
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
createTPoS: function () { |
|||
var data = { |
|||
name: this.formDialog.data.name, |
|||
currency: this.formDialog.data.currency |
|||
}; |
|||
var self = this; |
|||
return mapTPoS(obj) |
|||
}) |
|||
}) |
|||
}, |
|||
createTPoS: function () { |
|||
var data = { |
|||
name: this.formDialog.data.name, |
|||
currency: this.formDialog.data.currency |
|||
} |
|||
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); |
|||
}); |
|||
}, |
|||
deleteTPoS: function (tposId) { |
|||
var self = this; |
|||
var tpos = _.findWhere(this.tposs, {id: tposId}); |
|||
) |
|||
.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}) |
|||
|
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs); |
|||
} |
|||
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) |
|||
}) |
|||
}) |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getTPoSs(); |
|||
} |
|||
exportCSV: function () { |
|||
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs) |
|||
} |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getTPoSs() |
|||
} |
|||
}); |
|||
</script> |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -1,225 +1,265 @@ |
|||
{% 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"> |
|||
<div class="row justify-center full-width"> |
|||
<div class="col-12 col-sm-8 col-md-6 col-lg-4 text-center"> |
|||
<h3 class="q-mb-md">{% raw %}{{ famount }}{% endraw %}</h3> |
|||
<h5 class="q-mt-none"> |
|||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
|||
</h5> |
|||
</div> |
|||
{% 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"> |
|||
<div class="row justify-center full-width"> |
|||
<div class="col-12 col-sm-8 col-md-6 col-lg-4 text-center"> |
|||
<h3 class="q-mb-md">{% raw %}{{ famount }}{% endraw %}</h3> |
|||
<h5 class="q-mt-none"> |
|||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
|||
</h5> |
|||
</div> |
|||
</q-page-sticky> |
|||
<q-page-sticky expand position="bottom"> |
|||
<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 |
|||
@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 |
|||
:disabled="amount == 0" |
|||
@click="showInvoice()" |
|||
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 |
|||
@click="urlDialog.show = true" |
|||
size="xl" color="grey-7">#</q-btn> |
|||
</div> |
|||
</div> |
|||
</q-page-sticky> |
|||
<q-page-sticky expand position="bottom"> |
|||
<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 |
|||
@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 |
|||
:disabled="amount == 0" |
|||
@click="showInvoice()" |
|||
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 |
|||
@click="urlDialog.show = true" |
|||
size="xl" |
|||
color="grey-7" |
|||
>#</q-btn |
|||
> |
|||
</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-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
|||
<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> |
|||
<h5 class="q-mt-none"> |
|||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
|||
</h5> |
|||
</div> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
|||
</div> |
|||
</q-card> |
|||
</q-dialog> |
|||
<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> |
|||
</q-responsive> |
|||
<div class="text-center q-mb-xl"> |
|||
<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 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 %} |
|||
<style> |
|||
.keypad { |
|||
display: grid; |
|||
grid-gap: 8px; |
|||
grid-template-columns: repeat(4, 1fr); |
|||
grid-template-rows: repeat(4, 1fr); |
|||
} |
|||
.keypad .btn { |
|||
height: 100%; |
|||
} |
|||
.btn-cancel, .btn-confirm { |
|||
grid-row: auto/span 2; |
|||
} |
|||
</style> |
|||
{% 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); |
|||
</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-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
|||
<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> |
|||
<h5 class="q-mt-none"> |
|||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
|||
</h5> |
|||
</div> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
|||
</div> |
|||
</q-card> |
|||
</q-dialog> |
|||
<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> |
|||
</q-responsive> |
|||
<div class="text-center q-mb-xl"> |
|||
<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 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 %} |
|||
<style> |
|||
.keypad { |
|||
display: grid; |
|||
grid-gap: 8px; |
|||
grid-template-columns: repeat(4, 1fr); |
|||
grid-template-rows: repeat(4, 1fr); |
|||
} |
|||
.keypad .btn { |
|||
height: 100%; |
|||
} |
|||
.btn-cancel, |
|||
.btn-confirm { |
|||
grid-row: auto/span 2; |
|||
} |
|||
</style> |
|||
{% 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) |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
tposId: '{{ tpos.id }}', |
|||
currency: '{{ tpos.currency }}', |
|||
exchangeRate: null, |
|||
stack: [], |
|||
invoiceDialog: { |
|||
show: false, |
|||
data: null, |
|||
dismissMsg: null, |
|||
paymentChecker: null |
|||
}, |
|||
urlDialog: { |
|||
show: false |
|||
} |
|||
}; |
|||
}, |
|||
computed: { |
|||
amount: function () { |
|||
if (!this.stack.length) return 0.00; |
|||
return (Number(this.stack.join('')) / 100).toFixed(2); |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
tposId: '{{ tpos.id }}', |
|||
currency: '{{ tpos.currency }}', |
|||
exchangeRate: null, |
|||
stack: [], |
|||
invoiceDialog: { |
|||
show: false, |
|||
data: null, |
|||
dismissMsg: null, |
|||
paymentChecker: null |
|||
}, |
|||
famount: function () { |
|||
return LNbits.utils.formatCurrency(this.amount, this.currency); |
|||
}, |
|||
sat: function () { |
|||
if (!this.exchangeRate) return 0; |
|||
return Math.ceil((this.amount / this.exchangeRate) * 100000000); |
|||
}, |
|||
fsat: function () { |
|||
return LNbits.utils.formatSat(this.sat); |
|||
urlDialog: { |
|||
show: false |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
amount: function () { |
|||
if (!this.stack.length) return 0.0 |
|||
return (Number(this.stack.join('')) / 100).toFixed(2) |
|||
}, |
|||
methods: { |
|||
closeInvoiceDialog: function () { |
|||
this.stack = []; |
|||
var dialog = this.invoiceDialog; |
|||
setTimeout(function () { |
|||
clearInterval(dialog.paymentChecker); |
|||
dialog.dismissMsg(); |
|||
}, 3000); |
|||
}, |
|||
showInvoice: function () { |
|||
var self = this; |
|||
var dialog = this.invoiceDialog; |
|||
famount: function () { |
|||
return LNbits.utils.formatCurrency(this.amount, this.currency) |
|||
}, |
|||
sat: function () { |
|||
if (!this.exchangeRate) return 0 |
|||
return Math.ceil((this.amount / this.exchangeRate) * 100000000) |
|||
}, |
|||
fsat: function () { |
|||
return LNbits.utils.formatSat(this.sat) |
|||
} |
|||
}, |
|||
methods: { |
|||
closeInvoiceDialog: function () { |
|||
this.stack = [] |
|||
var dialog = this.invoiceDialog |
|||
setTimeout(function () { |
|||
clearInterval(dialog.paymentChecker) |
|||
dialog.dismissMsg() |
|||
}, 3000) |
|||
}, |
|||
showInvoice: function () { |
|||
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) { |
|||
if (res.data.paid) { |
|||
clearInterval(dialog.paymentChecker); |
|||
dialog.dismissMsg(); |
|||
dialog.show = false; |
|||
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 |
|||
|
|||
self.$q.notify({ |
|||
type: 'positive', |
|||
message: self.fsat + ' sat received!', |
|||
icon: null |
|||
}); |
|||
} |
|||
}); |
|||
}, 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]; |
|||
}); |
|||
} |
|||
self.$q.notify({ |
|||
type: 'positive', |
|||
message: self.fsat + ' sat received!', |
|||
icon: null |
|||
}) |
|||
} |
|||
}) |
|||
}, 2000) |
|||
}) |
|||
.catch(function (error) { |
|||
LNbits.utils.notifyApiError(error) |
|||
}) |
|||
}, |
|||
created: function () { |
|||
var getRates = this.getRates; |
|||
getRates(); |
|||
setInterval(function () { getRates(); }, 20000); |
|||
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] |
|||
}) |
|||
} |
|||
}); |
|||
</script> |
|||
}, |
|||
created: function () { |
|||
var getRates = this.getRates |
|||
getRates() |
|||
setInterval(function () { |
|||
getRates() |
|||
}, 20000) |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -1,416 +1,453 @@ |
|||
{% 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> |
|||
</q-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<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> |
|||
</div> |
|||
{% 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> |
|||
</q-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">Users</h5> |
|||
</div> |
|||
<q-table dense flat |
|||
:data="users" |
|||
row-key="id" |
|||
:columns="usersTable.columns" |
|||
: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" |
|||
> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></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" |
|||
> |
|||
{{ 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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<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> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportUsersCSV" |
|||
>Export to CSV</q-btn |
|||
> |
|||
</div> |
|||
<q-table dense flat |
|||
:data="wallets" |
|||
row-key="id" |
|||
:columns="walletsTable.columns" |
|||
: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" |
|||
> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
<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-tooltip> |
|||
Link to wallet |
|||
</q-tooltip> |
|||
|
|||
</q-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
|
|||
|
|||
</div> |
|||
|
|||
<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 User Manager Extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "usermanager/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
|
|||
<q-dialog v-model="userDialog.show" position="top"> |
|||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> |
|||
<q-form @submit="sendUserFormData" class="q-gutter-md"> |
|||
<q-input filled dense |
|||
v-model.trim="userDialog.data.usrname" |
|||
label="Username"></q-input> |
|||
<q-input filled dense |
|||
v-model.trim="userDialog.data.walname" |
|||
label="Initial wallet name"></q-input> |
|||
|
|||
<q-btn unelevated |
|||
color="deep-purple" |
|||
:disable="userDialog.data.walname == null" |
|||
type="submit">Create User</q-btn> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
|||
</div> |
|||
<q-table |
|||
dense |
|||
flat |
|||
:data="users" |
|||
row-key="id" |
|||
:columns="usersTable.columns" |
|||
: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"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></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"> |
|||
{{ 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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
|
|||
<q-card> |
|||
<q-card-section> |
|||
<div class="row items-center no-wrap q-mb-md"> |
|||
<div class="col"> |
|||
<h5 class="text-subtitle1 q-my-none">Wallets</h5> |
|||
</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-form @submit="sendWalletFormData" class="q-gutter-md"> |
|||
<q-select filled dense emit-value v-model="walletDialog.data.user" :options="userOptions" label="User *"> |
|||
</q-select> |
|||
<q-input filled dense |
|||
v-model.trim="walletDialog.data.walname" |
|||
label="Wallet name"></q-input> |
|||
<q-btn unelevated |
|||
color="deep-purple" |
|||
:disable="walletDialog.data.walname == null" |
|||
type="submit">Create Wallet</q-btn> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
|||
<div class="col-auto"> |
|||
<q-btn flat color="grey" @click="exportWalletsCSV" |
|||
>Export to CSV</q-btn |
|||
> |
|||
</div> |
|||
</q-form> |
|||
</q-card> |
|||
</q-dialog> |
|||
|
|||
|
|||
</div> |
|||
<q-table |
|||
dense |
|||
flat |
|||
:data="wallets" |
|||
row-key="id" |
|||
:columns="walletsTable.columns" |
|||
: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"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
<q-th auto-width></q-th> |
|||
</q-tr> |
|||
</template> |
|||
<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-tooltip> |
|||
Link to wallet |
|||
</q-tooltip> |
|||
</q-td> |
|||
<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-td> |
|||
</q-tr> |
|||
</template> |
|||
{% endraw %} |
|||
</q-table> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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; |
|||
} |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
|
|||
wallets: [], |
|||
users: [], |
|||
|
|||
usersTable: { |
|||
columns: [ |
|||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
|||
{name: 'name', align: 'left', label: 'Username', field: 'name'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
walletsTable: { |
|||
columns: [ |
|||
{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: 'inkey', align: 'left', label: 'Invoice Key', field: 'inkey'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
walletDialog: { |
|||
show: false, |
|||
data: {} |
|||
}, |
|||
userDialog: { |
|||
show: false, |
|||
data: {} |
|||
}, |
|||
}; |
|||
}, |
|||
computed: { |
|||
userOptions: function () { |
|||
return this.users.map(function (obj) { |
|||
console.log(obj.id) |
|||
return { |
|||
value: String(obj.id), |
|||
label: String(obj.id) |
|||
|
|||
}; |
|||
}); |
|||
}, |
|||
}, |
|||
methods: { |
|||
|
|||
///////////////Users//////////////////////////// |
|||
|
|||
<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 User Manager Extension</h6> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "usermanager/_api_docs.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
|
|||
getUsers: function () { |
|||
<q-dialog v-model="userDialog.show" position="top"> |
|||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;"> |
|||
<q-form @submit="sendUserFormData" class="q-gutter-md"> |
|||
<q-input |
|||
filled |
|||
dense |
|||
v-model.trim="userDialog.data.usrname" |
|||
label="Username" |
|||
></q-input> |
|||
<q-input |
|||
filled |
|||
dense |
|||
v-model.trim="userDialog.data.walname" |
|||
label="Initial wallet name" |
|||
></q-input> |
|||
|
|||
<q-btn |
|||
unelevated |
|||
color="deep-purple" |
|||
:disable="userDialog.data.walname == null" |
|||
type="submit" |
|||
>Create User</q-btn |
|||
> |
|||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
|||
</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-form @submit="sendWalletFormData" class="q-gutter-md"> |
|||
<q-select |
|||
filled |
|||
dense |
|||
emit-value |
|||
v-model="walletDialog.data.user" |
|||
:options="userOptions" |
|||
label="User *" |
|||
> |
|||
</q-select> |
|||
<q-input |
|||
filled |
|||
dense |
|||
v-model.trim="walletDialog.data.walname" |
|||
label="Wallet name" |
|||
></q-input> |
|||
<q-btn |
|||
unelevated |
|||
color="deep-purple" |
|||
:disable="walletDialog.data.walname == null" |
|||
type="submit" |
|||
>Create Wallet</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) }} |
|||
<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 |
|||
} |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin], |
|||
data: function () { |
|||
return { |
|||
wallets: [], |
|||
users: [], |
|||
|
|||
usersTable: { |
|||
columns: [ |
|||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
|||
{name: 'name', align: 'left', label: 'Username', field: 'name'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
walletsTable: { |
|||
columns: [ |
|||
{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: 'inkey', align: 'left', label: 'Invoice Key', field: 'inkey'} |
|||
], |
|||
pagination: { |
|||
rowsPerPage: 10 |
|||
} |
|||
}, |
|||
walletDialog: { |
|||
show: false, |
|||
data: {} |
|||
}, |
|||
userDialog: { |
|||
show: false, |
|||
data: {} |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
userOptions: function () { |
|||
return this.users.map(function (obj) { |
|||
console.log(obj.id) |
|||
return { |
|||
value: String(obj.id), |
|||
label: String(obj.id) |
|||
} |
|||
}) |
|||
} |
|||
}, |
|||
methods: { |
|||
///////////////Users//////////////////////////// |
|||
|
|||
var self = this; |
|||
getUsers: function () { |
|||
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}); |
|||
|
|||
this.userDialog.data = _.clone(link._data); |
|||
this.userDialog.show = true; |
|||
}, |
|||
sendUserFormData: function () { |
|||
if (this.userDialog.data.id){} |
|||
else{ |
|||
openUserUpdateDialog: function (linkId) { |
|||
var link = _.findWhere(this.users, {id: linkId}) |
|||
|
|||
this.userDialog.data = _.clone(link._data) |
|||
this.userDialog.show = true |
|||
}, |
|||
sendUserFormData: function () { |
|||
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( |
|||
createUser: function (data) { |
|||
var self = this |
|||
LNbits.api |
|||
.request( |
|||
'POST', |
|||
'/usermanager/api/v1/users', |
|||
this.g.user.wallets[0].inkey, |
|||
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); |
|||
}); |
|||
}, |
|||
deleteUser: function (userId) { |
|||
var self = this; |
|||
|
|||
console.log(userId) |
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
|
|||
exportUsersCSV: function () { |
|||
LNbits.utils.exportCSV(this.usersTable.columns, this.users); |
|||
}, |
|||
) |
|||
.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 |
|||
|
|||
console.log(userId) |
|||
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) |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
exportUsersCSV: function () { |
|||
LNbits.utils.exportCSV(this.usersTable.columns, this.users) |
|||
}, |
|||
|
|||
///////////////Wallets//////////////////////////// |
|||
///////////////Wallets//////////////////////////// |
|||
|
|||
getWallets: function () { |
|||
var self = this; |
|||
getWallets: function () { |
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
openWalletUpdateDialog: function (linkId) { |
|||
var link = _.findWhere(this.users, {id: linkId}); |
|||
return mapUserManager(obj) |
|||
}) |
|||
}) |
|||
}, |
|||
openWalletUpdateDialog: function (linkId) { |
|||
var link = _.findWhere(this.users, {id: linkId}) |
|||
|
|||
this.walletDialog.data = _.clone(link._data); |
|||
this.walletDialog.show = true; |
|||
}, |
|||
sendWalletFormData: function () { |
|||
if (this.walletDialog.data.id){} |
|||
else{ |
|||
this.walletDialog.data = _.clone(link._data) |
|||
this.walletDialog.show = true |
|||
}, |
|||
sendWalletFormData: function () { |
|||
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( |
|||
createWallet: function (data) { |
|||
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); |
|||
}); |
|||
}, |
|||
deleteWallet: function (userId) { |
|||
var self = this; |
|||
|
|||
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); |
|||
}); |
|||
}); |
|||
}, |
|||
exportWalletsCSV: function () { |
|||
LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets); |
|||
} |
|||
|
|||
) |
|||
.then(function (response) { |
|||
self.wallets.push(mapUserManager(response.data)) |
|||
self.walletDialog.show = false |
|||
self.walletDialog.data = {} |
|||
data = {} |
|||
}) |
|||
.catch(function (error) { |
|||
LNbits.utils.notifyApiError(error) |
|||
}) |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getUsers(); |
|||
this.getWallets(); |
|||
} |
|||
deleteWallet: function (userId) { |
|||
var self = this |
|||
|
|||
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) |
|||
}) |
|||
}) |
|||
}, |
|||
exportWalletsCSV: function () { |
|||
LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets) |
|||
} |
|||
}); |
|||
|
|||
</script> |
|||
}, |
|||
created: function () { |
|||
if (this.g.user.wallets.length) { |
|||
this.getUsers() |
|||
this.getWallets() |
|||
} |
|||
} |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -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> |
|||
|
@ -1,52 +1,57 @@ |
|||
{% 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"> |
|||
<div class="text-center"> |
|||
{% if link.is_spent %} |
|||
<q-badge color="red" class="q-mb-md">Withdraw is spent.</q-badge> |
|||
{% 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> |
|||
</q-responsive> |
|||
</a> |
|||
</div> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')">Copy LNURL</q-btn> |
|||
</div> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
<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> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "withdraw/_lnurl.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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"> |
|||
<div class="text-center"> |
|||
{% if link.is_spent %} |
|||
<q-badge color="red" class="q-mb-md">Withdraw is spent.</q-badge> |
|||
{% 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> |
|||
</q-responsive> |
|||
</a> |
|||
</div> |
|||
<div class="row q-mt-lg"> |
|||
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')" |
|||
>Copy LNURL</q-btn |
|||
> |
|||
</div> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
{% 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); |
|||
<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> |
|||
</q-card-section> |
|||
<q-card-section class="q-pa-none"> |
|||
<q-separator></q-separator> |
|||
<q-list> |
|||
{% include "withdraw/_lnurl.html" %} |
|||
</q-list> |
|||
</q-card-section> |
|||
</q-card> |
|||
</div> |
|||
</div> |
|||
{% 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) |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin] |
|||
}); |
|||
</script> |
|||
new Vue({ |
|||
el: '#vue', |
|||
mixins: [windowMixin] |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -1,66 +1,59 @@ |
|||
{% 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> |
|||
</div> |
|||
<img src="{{ url_for('static', filename='images/note.jpg') }}"> |
|||
</div> |
|||
{% endfor %} |
|||
{% 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> |
|||
</div> |
|||
<img src="{{ url_for('static', filename='images/note.jpg') }}" /> |
|||
</div> |
|||
{% endfor %} |
|||
</div> |
|||
{% endblock %} |
|||
</div> |
|||
{% endblock %} {% block styles %} |
|||
<style> |
|||
.zimbabwe { |
|||
page-break-inside: avoid; |
|||
height: 7cm; |
|||
width: 16cm; |
|||
position: relative; |
|||
margin-bottom: 10px; |
|||
overflow: hidden; |
|||
} |
|||
.zimbabwe img { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 0; |
|||
width: 100%; |
|||
} |
|||
.zimbabwe .qr { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
right: 0; |
|||
z-index: 10; |
|||
background: rgb(255, 255, 255, 0.7); |
|||
padding: 10px; |
|||
text-align: center; |
|||
line-height: 1.1; |
|||
} |
|||
</style> |
|||
{% 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) |
|||
|
|||
{% block styles %} |
|||
<style> |
|||
.zimbabwe { |
|||
page-break-inside: avoid; |
|||
height: 7cm; |
|||
width: 16cm; |
|||
position: relative; |
|||
margin-bottom: 10px; |
|||
overflow: hidden; |
|||
} |
|||
.zimbabwe img { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 0; |
|||
width: 100%; |
|||
new Vue({ |
|||
el: '#vue', |
|||
created: function () { |
|||
window.print() |
|||
} |
|||
.zimbabwe .qr { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
right: 0; |
|||
z-index: 10; |
|||
background: rgb(255, 255, 255, 0.7); |
|||
padding: 10px; |
|||
text-align: center; |
|||
line-height: 1.1; |
|||
} |
|||
</style> |
|||
{% 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); |
|||
|
|||
new Vue({ |
|||
el: '#vue', |
|||
created: function () { |
|||
window.print(); |
|||
} |
|||
}); |
|||
</script> |
|||
}) |
|||
</script> |
|||
{% endblock %} |
|||
|
@ -0,0 +1,5 @@ |
|||
{ |
|||
"devDependencies": { |
|||
"prettier": "^2.0.5" |
|||
} |
|||
} |
Loading…
Reference in new issue