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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin] |
mixins: [windowMixin] |
||||
}); |
}) |
||||
|
@ -1,16 +1,24 @@ |
|||||
|
<q-expansion-item |
||||
<q-expansion-item |
group="extras" |
||||
group="extras" |
icon="swap_vertical_circle" |
||||
icon="swap_vertical_circle" |
label="Info" |
||||
label="Info" |
:content-inset-level="0.5" |
||||
:content-inset-level="0.5" |
> |
||||
> |
<q-card> |
||||
<q-card> |
<q-card-section> |
||||
<q-card-section> |
<h5 class="text-subtitle1 q-my-none">Assistant Faucet Milker</h5> |
||||
<h5 class="text-subtitle1 q-my-none">Assistant Faucet Milker</h5> |
<p> |
||||
<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/> |
Milking faucets with software, known as "assmilking", seems at first to |
||||
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p> |
be black-hat, although in fact there might be some unexplored use cases. |
||||
</q-card> |
An LNURL withdraw gives someone the right to pull funds, which can be |
||||
</q-card-section> |
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 |
||||
</q-card-section></q-expansion-item> |
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" %} |
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context |
||||
|
%} {% block page %} |
||||
{% from "macros.jinja" import window_vars with context %} |
<div class="row q-col-gutter-md"> |
||||
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
||||
|
<q-card> |
||||
{% block page %} |
<q-card-section> |
||||
<div class="row q-col-gutter-md"> |
<q-btn unelevated color="deep-purple" @click="amilkDialog.show = true" |
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
>New AMilk</q-btn |
||||
<q-card> |
> |
||||
<q-card-section> |
</q-card-section> |
||||
<q-btn unelevated color="deep-purple" @click="amilkDialog.show = true">New AMilk</q-btn> |
</q-card> |
||||
</q-card-section> |
|
||||
</q-card> |
<q-card> |
||||
|
<q-card-section> |
||||
<q-card> |
<div class="row items-center no-wrap q-mb-md"> |
||||
<q-card-section> |
<div class="col"> |
||||
<div class="row items-center no-wrap q-mb-md"> |
<h5 class="text-subtitle1 q-my-none">AMilks</h5> |
||||
<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> |
|
||||
</div> |
</div> |
||||
<q-table dense flat |
<div class="col-auto"> |
||||
:data="amilks" |
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
||||
row-key="id" |
</div> |
||||
:columns="amilksTable.columns" |
</div> |
||||
:pagination.sync="amilksTable.pagination"> |
<q-table |
||||
{% raw %} |
dense |
||||
<template v-slot:header="props"> |
flat |
||||
<q-tr :props="props"> |
:data="amilks" |
||||
|
row-key="id" |
||||
<q-th |
:columns="amilksTable.columns" |
||||
v-for="col in props.cols" |
:pagination.sync="amilksTable.pagination" |
||||
:key="col.name" |
> |
||||
:props="props" |
{% raw %} |
||||
> |
<template v-slot:header="props"> |
||||
{{ col.label }} |
<q-tr :props="props"> |
||||
</q-th> |
<q-th v-for="col in props.cols" :key="col.name" :props="props"> |
||||
|
{{ col.label }} |
||||
</q-tr> |
</q-th> |
||||
</template> |
</q-tr> |
||||
<template v-slot:body="props"> |
</template> |
||||
<q-tr :props="props"> |
<template v-slot:body="props"> |
||||
|
<q-tr :props="props"> |
||||
<q-td |
<q-td v-for="col in props.cols" :key="col.name" :props="props"> |
||||
v-for="col in props.cols" |
{{ col.value }} |
||||
:key="col.name" |
</q-td> |
||||
:props="props" |
<q-td auto-width> |
||||
|
<q-btn |
||||
> |
flat |
||||
{{ col.value }} |
dense |
||||
</q-td> |
size="xs" |
||||
<q-td auto-width> |
@click="deleteAMilk(props.row.id)" |
||||
<q-btn flat dense size="xs" @click="deleteAMilk(props.row.id)" icon="cancel" color="pink"></q-btn> |
icon="cancel" |
||||
</q-td> |
color="pink" |
||||
</q-tr> |
></q-btn> |
||||
</template> |
</q-td> |
||||
{% endraw %} |
</q-tr> |
||||
</q-table> |
</template> |
||||
</q-card-section> |
{% endraw %} |
||||
</q-card> |
</q-table> |
||||
</div> |
</q-card-section> |
||||
|
</q-card> |
||||
<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> |
</div> |
||||
{% endblock %} |
|
||||
|
|
||||
{% block scripts %} |
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md"> |
||||
{{ window_vars(user) }} |
<q-card> |
||||
<script> |
<q-card-section> |
||||
var mapAMilk = function (obj) { |
<h6 class="text-subtitle1 q-my-none"> |
||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); |
LNbits Assistant Faucet Milker Extension |
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount); |
</h6> |
||||
obj.wall = ['/amilk/', obj.id].join(''); |
</q-card-section> |
||||
return obj; |
<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({ |
<q-dialog v-model="amilkDialog.show" position="top"> |
||||
el: '#vue', |
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> |
||||
mixins: [windowMixin], |
<q-form @submit="createAMilk" class="q-gutter-md"> |
||||
data: function () { |
<q-select |
||||
return { |
filled |
||||
amilks: [], |
dense |
||||
amilksTable: { |
emit-value |
||||
columns: [ |
v-model="amilkDialog.data.wallet" |
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
:options="g.user.walletOptions" |
||||
{name: 'lnurl', align: 'left', label: 'LNURL', field: 'lnurl'}, |
label="Wallet *" |
||||
{name: 'atime', align: 'left', label: 'Freq', field: 'atime'}, |
> |
||||
{name: 'amount', align: 'left', label: 'Amount', field: 'amount'} |
</q-select> |
||||
], |
<q-input |
||||
pagination: { |
filled |
||||
rowsPerPage: 10 |
dense |
||||
} |
v-model.trim="amilkDialog.data.lnurl" |
||||
}, |
type="url" |
||||
amilkDialog: { |
label="LNURL Withdraw" |
||||
show: false, |
></q-input> |
||||
data: {} |
<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 |
||||
} |
} |
||||
}; |
}, |
||||
}, |
amilkDialog: { |
||||
methods: { |
show: false, |
||||
|
data: {} |
||||
|
} |
||||
|
} |
||||
getAMilks: function () { |
}, |
||||
var self = this; |
methods: { |
||||
|
getAMilks: function () { |
||||
|
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'GET', |
'GET', |
||||
'/amilk/api/v1/amilk?all_wallets', |
'/amilk/api/v1/amilk?all_wallets', |
||||
this.g.user.wallets[0].inkey |
this.g.user.wallets[0].inkey |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
self.amilks = response.data.map(function (obj) { |
self.amilks = response.data.map(function (obj) { |
||||
response.data.forEach(MILK); |
response.data.forEach(MILK) |
||||
function MILK(item){ |
function MILK(item) { |
||||
|
window.setInterval(function () { |
||||
window.setInterval(function(){ |
LNbits.api |
||||
|
.request( |
||||
|
'GET', |
||||
LNbits.api.request( |
'/amilk/api/v1/amilk/milk/' + item.id, |
||||
'GET', |
'Lorem' |
||||
'/amilk/api/v1/amilk/milk/' + item.id, |
) |
||||
"Lorem" |
.then(function (response) { |
||||
).then(function (response) { |
self.amilks = response.data.map(function (obj) { |
||||
self.amilks = response.data.map(function (obj) { |
return mapAMilk(obj) |
||||
return mapAMilk(obj); |
}) |
||||
}); |
}) |
||||
}); |
}, item.atime * 1000) |
||||
}, |
|
||||
item.atime*1000); |
|
||||
|
|
||||
} |
} |
||||
return mapAMilk(obj); |
return mapAMilk(obj) |
||||
}); |
}) |
||||
}); |
}) |
||||
}, |
}, |
||||
createAMilk: function () { |
createAMilk: function () { |
||||
var data = { |
var data = { |
||||
lnurl: this.amilkDialog.data.lnurl, |
lnurl: this.amilkDialog.data.lnurl, |
||||
atime: parseInt(this.amilkDialog.data.atime), |
atime: parseInt(this.amilkDialog.data.atime), |
||||
amount: this.amilkDialog.data.amount |
amount: this.amilkDialog.data.amount |
||||
}; |
} |
||||
var self = this; |
var self = this |
||||
|
|
||||
console.log(this.amilkDialog.data.wallet); |
console.log(this.amilkDialog.data.wallet) |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'POST', |
'POST', |
||||
'/amilk/api/v1/amilk', |
'/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 |
data |
||||
).then(function (response) { |
) |
||||
self.amilks.push(mapAMilk(response.data)); |
.then(function (response) { |
||||
self.amilkDialog.show = false; |
self.amilks.push(mapAMilk(response.data)) |
||||
self.amilkDialog.data = {}; |
self.amilkDialog.show = false |
||||
}).catch(function (error) { |
self.amilkDialog.data = {} |
||||
LNbits.utils.notifyApiError(error); |
}) |
||||
}); |
.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); |
|
||||
} |
|
||||
}, |
}, |
||||
created: function () { |
deleteAMilk: function (amilkId) { |
||||
if (this.g.user.wallets.length) { |
var self = this |
||||
this.getAMilks(); |
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 %} |
{% 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" %} |
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context |
||||
|
%} {% block page %} |
||||
{% from "macros.jinja" import window_vars with context %} |
<q-card> |
||||
|
<q-card-section> |
||||
|
<h5 class="text-subtitle1 q-mt-none q-mb-md">Frameworks used by LNbits</h5> |
||||
{% block page %} |
<q-list> |
||||
<q-card> |
<q-item |
||||
<q-card-section> |
v-for="tool in tools" |
||||
<h5 class="text-subtitle1 q-mt-none q-mb-md">Frameworks used by LNbits</h5> |
:key="tool.name" |
||||
<q-list> |
tag="a" |
||||
<q-item v-for="tool in tools" :key="tool.name" tag="a" :href="tool.url" target="_blank"> |
:href="tool.url" |
||||
{% raw %} <!-- with raw Flask won't try to interpret the Vue moustaches --> |
target="_blank" |
||||
<q-item-section> |
> |
||||
<q-item-label>{{ tool.name }}</q-item-label> |
{% raw %} |
||||
<q-item-label caption>{{ tool.language }}</q-item-label> |
<!-- with raw Flask won't try to interpret the Vue moustaches --> |
||||
</q-item-section> |
<q-item-section> |
||||
{% endraw %} |
<q-item-label>{{ tool.name }}</q-item-label> |
||||
</q-item> |
<q-item-label caption>{{ tool.language }}</q-item-label> |
||||
</q-list> |
</q-item-section> |
||||
<q-separator class="q-my-lg"></q-separator> |
{% endraw %} |
||||
<p>A magical "g" is always available, with info about the user, wallets and extensions:</p> |
</q-item> |
||||
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code> |
</q-list> |
||||
</q-card-section> |
<q-separator class="q-my-lg"></q-separator> |
||||
</q-card> |
<p> |
||||
{% endblock %} |
A magical "g" is always available, with info about the user, wallets and |
||||
|
extensions: |
||||
{% block scripts %} |
</p> |
||||
{{ window_vars(user) }} |
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code> |
||||
<script> |
</q-card-section> |
||||
new Vue({ |
</q-card> |
||||
el: '#vue', |
{% endblock %} {% block scripts %} {{ window_vars(user) }} |
||||
mixins: [windowMixin], |
<script> |
||||
data: function () { |
new Vue({ |
||||
return { |
el: '#vue', |
||||
tools: [] |
mixins: [windowMixin], |
||||
}; |
data: function () { |
||||
}, |
return { |
||||
created: function () { |
tools: [] |
||||
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> |
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 %} |
{% endblock %} |
||||
|
@ -1,107 +1,122 @@ |
|||||
{% extends "public.html" %} |
{% 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"> |
||||
{% block page %} |
<q-card class="q-pa-lg"> |
||||
<div class="row q-col-gutter-md justify-center"> |
<q-card-section class="q-pa-none"> |
||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4"> |
<h5 class="text-subtitle1 q-my-none">{{ paywall.memo }}</h5> |
||||
<q-card class="q-pa-lg"> |
<strong class="text-purple" |
||||
<q-card-section class="q-pa-none"> |
>Price: |
||||
<h5 class="text-subtitle1 q-my-none">{{ paywall.memo }}</h5> |
<lnbits-fsat :amount="{{ paywall.amount }}"></lnbits-fsat> sat</strong |
||||
<strong class="text-purple">Price: <lnbits-fsat :amount="{{ paywall.amount }}"></lnbits-fsat> sat</strong> |
> |
||||
<q-separator class="q-my-lg"></q-separator> |
<q-separator class="q-my-lg"></q-separator> |
||||
<div v-if="paymentReq"> |
<div v-if="paymentReq"> |
||||
<a :href="'lightning:' + paymentReq"> |
<a :href="'lightning:' + paymentReq"> |
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
||||
<qrcode :value="paymentReq" :options="{width: 800}" class="rounded-borders"></qrcode> |
<qrcode |
||||
</q-responsive> |
:value="paymentReq" |
||||
</a> |
:options="{width: 800}" |
||||
<div class="row q-mt-lg"> |
class="rounded-borders" |
||||
<q-btn outline color="grey" @click="copyText(paymentReq)">Copy invoice</q-btn> |
></qrcode> |
||||
</div> |
</q-responsive> |
||||
|
</a> |
||||
|
<div class="row q-mt-lg"> |
||||
|
<q-btn outline color="grey" @click="copyText(paymentReq)" |
||||
|
>Copy invoice</q-btn |
||||
|
> |
||||
</div> |
</div> |
||||
<div v-if="redirectUrl"> |
</div> |
||||
<p>You can access the URL behind this paywall:<br> |
<div v-if="redirectUrl"> |
||||
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong></p> |
<p> |
||||
<div class="row q-mt-lg"> |
You can access the URL behind this paywall:<br /> |
||||
<q-btn outline color="grey" type="a" :href="redirectUrl">Open URL</q-btn> |
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong> |
||||
</div> |
</p> |
||||
|
<div class="row q-mt-lg"> |
||||
|
<q-btn outline color="grey" type="a" :href="redirectUrl" |
||||
|
>Open URL</q-btn |
||||
|
> |
||||
</div> |
</div> |
||||
</q-card-section> |
</div> |
||||
</q-card> |
</q-card-section> |
||||
</div> |
</q-card> |
||||
<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> |
</div> |
||||
{% endblock %} |
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md"> |
||||
|
<q-card> |
||||
{% block scripts %} |
<q-card-section> |
||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> |
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits paywall</h6> |
||||
<script> |
</q-card-section> |
||||
Vue.component(VueQrcode.name, VueQrcode); |
</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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin], |
mixins: [windowMixin], |
||||
data: function () { |
data: function () { |
||||
return { |
return { |
||||
paymentReq: null, |
paymentReq: null, |
||||
redirectUrl: null |
redirectUrl: null |
||||
}; |
} |
||||
}, |
}, |
||||
methods: { |
methods: { |
||||
getInvoice: function () { |
getInvoice: function () { |
||||
var self = this; |
var self = this |
||||
|
|
||||
axios.get( |
axios |
||||
'/paywall/api/v1/paywalls/{{ paywall.id }}/invoice' |
.get('/paywall/api/v1/paywalls/{{ paywall.id }}/invoice') |
||||
).then(function (response) { |
.then(function (response) { |
||||
self.paymentReq = response.data.payment_request; |
self.paymentReq = response.data.payment_request |
||||
|
|
||||
dismissMsg = self.$q.notify({ |
dismissMsg = self.$q.notify({ |
||||
timeout: 0, |
timeout: 0, |
||||
message: 'Waiting for payment...' |
message: 'Waiting for payment...' |
||||
}); |
}) |
||||
|
|
||||
paymentChecker = setInterval(function () { |
paymentChecker = setInterval(function () { |
||||
axios.post( |
axios |
||||
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice', |
.post( |
||||
{checking_id: response.data.checking_id} |
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice', |
||||
).then(function (res) { |
{checking_id: response.data.checking_id} |
||||
if (res.data.paid) { |
) |
||||
clearInterval(paymentChecker); |
.then(function (res) { |
||||
dismissMsg(); |
if (res.data.paid) { |
||||
self.redirectUrl = res.data.url; |
clearInterval(paymentChecker) |
||||
self.$q.localStorage.set('lnbits.paywall.{{ paywall.id }}', res.data.url); |
dismissMsg() |
||||
|
self.redirectUrl = res.data.url |
||||
|
self.$q.localStorage.set( |
||||
|
'lnbits.paywall.{{ paywall.id }}', |
||||
|
res.data.url |
||||
|
) |
||||
|
|
||||
self.$q.notify({ |
self.$q.notify({ |
||||
type: 'positive', |
type: 'positive', |
||||
message: 'Payment received!', |
message: 'Payment received!', |
||||
icon: null |
icon: null |
||||
}); |
}) |
||||
} |
} |
||||
}).catch(function (error) { |
}) |
||||
LNbits.utils.notifyApiError(error); |
.catch(function (error) { |
||||
}); |
LNbits.utils.notifyApiError(error) |
||||
}, 2000); |
}) |
||||
}).catch(function (error) { |
}, 2000) |
||||
LNbits.utils.notifyApiError(error); |
}) |
||||
}); |
.catch(function (error) { |
||||
} |
LNbits.utils.notifyApiError(error) |
||||
}, |
}) |
||||
created: function () { |
} |
||||
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}'); |
}, |
||||
|
created: function () { |
||||
|
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}') |
||||
|
|
||||
if (url) { |
if (url) { |
||||
this.redirectUrl = url; |
this.redirectUrl = url |
||||
} else { |
} else { |
||||
this.getInvoice(); |
this.getInvoice() |
||||
}; |
|
||||
} |
} |
||||
}); |
} |
||||
</script> |
}) |
||||
|
</script> |
||||
{% endblock %} |
{% 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 %} |
<q-card> |
||||
|
<q-card-section> |
||||
|
<div class="row items-center no-wrap q-mb-md"> |
||||
{% block page %} |
<div class="col"> |
||||
<div class="row q-col-gutter-md"> |
<h5 class="text-subtitle1 q-my-none">Paywalls</h5> |
||||
<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> |
|
||||
</div> |
</div> |
||||
<q-table dense flat |
<div class="col-auto"> |
||||
:data="paywalls" |
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
||||
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> |
</div> |
||||
</q-form> |
</div> |
||||
</q-card> |
<q-table |
||||
</q-dialog> |
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> |
||||
{% endblock %} |
|
||||
|
|
||||
{% block scripts %} |
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md"> |
||||
{{ window_vars(user) }} |
<q-card> |
||||
<script> |
<q-card-section> |
||||
var mapPaywall = function (obj) { |
<h6 class="text-subtitle1 q-my-none">LNbits paywall extension</h6> |
||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); |
</q-card-section> |
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount); |
<q-card-section class="q-pa-none"> |
||||
obj.displayUrl = ['/paywall/', obj.id].join(''); |
<q-separator></q-separator> |
||||
return obj; |
<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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin], |
mixins: [windowMixin], |
||||
data: function () { |
data: function () { |
||||
return { |
return { |
||||
paywalls: [], |
paywalls: [], |
||||
paywallsTable: { |
paywallsTable: { |
||||
columns: [ |
columns: [ |
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
||||
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'}, |
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'}, |
||||
{name: 'date', align: 'left', label: 'Date', field: 'date', sortable: true}, |
{ |
||||
{ |
name: 'date', |
||||
name: 'amount', align: 'right', label: 'Amount (sat)', field: 'fsat', sortable: true, |
align: 'left', |
||||
sort: function (a, b, rowA, rowB) { |
label: 'Date', |
||||
return rowA.amount - rowB.amount; |
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: { |
pagination: { |
||||
show: false, |
rowsPerPage: 10 |
||||
data: {} |
|
||||
} |
} |
||||
}; |
}, |
||||
}, |
formDialog: { |
||||
methods: { |
show: false, |
||||
getPaywalls: function () { |
data: {} |
||||
var self = this; |
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
getPaywalls: function () { |
||||
|
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'GET', |
'GET', |
||||
'/paywall/api/v1/paywalls?all_wallets', |
'/paywall/api/v1/paywalls?all_wallets', |
||||
this.g.user.wallets[0].inkey |
this.g.user.wallets[0].inkey |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
self.paywalls = response.data.map(function (obj) { |
self.paywalls = response.data.map(function (obj) { |
||||
return mapPaywall(obj); |
return mapPaywall(obj) |
||||
}); |
}) |
||||
}); |
}) |
||||
}, |
}, |
||||
createPaywall: function () { |
createPaywall: function () { |
||||
var data = { |
var data = { |
||||
url: this.formDialog.data.url, |
url: this.formDialog.data.url, |
||||
memo: this.formDialog.data.memo, |
memo: this.formDialog.data.memo, |
||||
amount: this.formDialog.data.amount |
amount: this.formDialog.data.amount |
||||
}; |
} |
||||
var self = this; |
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'POST', |
'POST', |
||||
'/paywall/api/v1/paywalls', |
'/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 |
data |
||||
).then(function (response) { |
) |
||||
self.paywalls.push(mapPaywall(response.data)); |
.then(function (response) { |
||||
self.formDialog.show = false; |
self.paywalls.push(mapPaywall(response.data)) |
||||
self.formDialog.data = {}; |
self.formDialog.show = false |
||||
}).catch(function (error) { |
self.formDialog.data = {} |
||||
LNbits.utils.notifyApiError(error); |
}) |
||||
}); |
.catch(function (error) { |
||||
}, |
LNbits.utils.notifyApiError(error) |
||||
deletePaywall: function (paywallId) { |
}) |
||||
var self = this; |
}, |
||||
var paywall = _.findWhere(this.paywalls, {id: paywallId}); |
deletePaywall: function (paywallId) { |
||||
|
var self = this |
||||
|
var paywall = _.findWhere(this.paywalls, {id: paywallId}) |
||||
|
|
||||
LNbits.utils.confirmDialog( |
LNbits.utils |
||||
'Are you sure you want to delete this paywall link?' |
.confirmDialog('Are you sure you want to delete this paywall link?') |
||||
).onOk(function () { |
.onOk(function () { |
||||
LNbits.api.request( |
LNbits.api |
||||
'DELETE', |
.request( |
||||
'/paywall/api/v1/paywalls/' + paywallId, |
'DELETE', |
||||
_.findWhere(self.g.user.wallets, {id: paywall.wallet}).inkey |
'/paywall/api/v1/paywalls/' + paywallId, |
||||
).then(function (response) { |
_.findWhere(self.g.user.wallets, {id: paywall.wallet}).inkey |
||||
self.paywalls = _.reject(self.paywalls, function (obj) { return obj.id == paywallId; }); |
) |
||||
}).catch(function (error) { |
.then(function (response) { |
||||
LNbits.utils.notifyApiError(error); |
self.paywalls = _.reject(self.paywalls, function (obj) { |
||||
}); |
return obj.id == paywallId |
||||
}); |
}) |
||||
}, |
}) |
||||
exportCSV: function () { |
.catch(function (error) { |
||||
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls); |
LNbits.utils.notifyApiError(error) |
||||
} |
}) |
||||
|
}) |
||||
}, |
}, |
||||
created: function () { |
exportCSV: function () { |
||||
if (this.g.user.wallets.length) { |
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls) |
||||
this.getPaywalls(); |
|
||||
} |
|
||||
} |
} |
||||
}); |
}, |
||||
</script> |
created: function () { |
||||
|
if (this.g.user.wallets.length) { |
||||
|
this.getPaywalls() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -1,11 +1,18 @@ |
|||||
<q-expansion-item |
<q-expansion-item group="extras" icon="info" label="About TPoS"> |
||||
group="extras" |
|
||||
icon="info" |
|
||||
label="About TPoS"> |
|
||||
<q-card> |
<q-card> |
||||
<q-card-section> |
<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> |
<p> |
||||
<small>Created by <a href="https://github.com/talvasconcelos" target="_blank">Tiago Vasconcelos</a>.</small> |
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-section> |
||||
</q-card> |
</q-card> |
||||
</q-expansion-item> |
</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 %} |
<q-card> |
||||
|
<q-card-section> |
||||
|
<div class="row items-center no-wrap q-mb-md"> |
||||
{% block page %} |
<div class="col"> |
||||
<div class="row q-col-gutter-md"> |
<h5 class="text-subtitle1 q-my-none">TPoS</h5> |
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
</div> |
||||
<q-card> |
<div class="col-auto"> |
||||
<q-card-section> |
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn> |
||||
<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> |
|
||||
</div> |
</div> |
||||
<q-table dense flat |
</div> |
||||
:data="tposs" |
<q-table |
||||
row-key="id" |
dense |
||||
:columns="tpossTable.columns" |
flat |
||||
:pagination.sync="tpossTable.pagination"> |
:data="tposs" |
||||
{% raw %} |
row-key="id" |
||||
<template v-slot:header="props"> |
:columns="tpossTable.columns" |
||||
<q-tr :props="props"> |
:pagination.sync="tpossTable.pagination" |
||||
<q-th auto-width></q-th> |
> |
||||
<q-th |
{% raw %} |
||||
v-for="col in props.cols" |
<template v-slot:header="props"> |
||||
:key="col.name" |
<q-tr :props="props"> |
||||
:props="props" |
<q-th auto-width></q-th> |
||||
> |
<q-th v-for="col in props.cols" :key="col.name" :props="props"> |
||||
{{ col.label }} |
{{ col.label }} |
||||
</q-th> |
</q-th> |
||||
<q-th auto-width></q-th> |
<q-th auto-width></q-th> |
||||
</q-tr> |
</q-tr> |
||||
</template> |
</template> |
||||
|
|
||||
<template v-slot:body="props"> |
<template v-slot:body="props"> |
||||
<q-tr :props="props"> |
<q-tr :props="props"> |
||||
<q-td auto-width> |
<q-td auto-width> |
||||
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.tpos" target="_blank"></q-btn> |
<q-btn |
||||
</q-td> |
unelevated |
||||
<q-td |
dense |
||||
v-for="col in props.cols" |
size="xs" |
||||
:key="col.name" |
icon="launch" |
||||
:props="props" |
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" |
||||
> |
type="a" |
||||
{{ col.value }} |
:href="props.row.tpos" |
||||
</q-td> |
target="_blank" |
||||
<q-td auto-width> |
></q-btn> |
||||
<q-btn flat dense size="xs" @click="deleteTPoS(props.row.id)" icon="cancel" color="pink"></q-btn> |
</q-td> |
||||
</q-td> |
<q-td v-for="col in props.cols" :key="col.name" :props="props"> |
||||
</q-tr> |
{{ col.value }} |
||||
</template> |
</q-td> |
||||
{% endraw %} |
<q-td auto-width> |
||||
</q-table> |
<q-btn |
||||
</q-card-section> |
flat |
||||
</q-card> |
dense |
||||
</div> |
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"> |
<div class="col-12 col-md-5 q-gutter-y-md"> |
||||
<q-card> |
<q-card> |
||||
<q-card-section> |
<q-card-section> |
||||
<h6 class="text-subtitle1 q-my-none">LNbits TPoS extension</h6> |
<h6 class="text-subtitle1 q-my-none">LNbits TPoS extension</h6> |
||||
</q-card-section> |
</q-card-section> |
||||
<q-card-section class="q-pa-none"> |
<q-card-section class="q-pa-none"> |
||||
|
<q-separator></q-separator> |
||||
|
<q-list> |
||||
|
{% include "tpos/_api_docs.html" %} |
||||
<q-separator></q-separator> |
<q-separator></q-separator> |
||||
<q-list> |
{% include "tpos/_tpos.html" %} |
||||
{% include "tpos/_api_docs.html" %} |
</q-list> |
||||
<q-separator></q-separator> |
</q-card-section> |
||||
{% include "tpos/_tpos.html" %} |
</q-card> |
||||
</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> |
|
||||
</div> |
</div> |
||||
{% endblock %} |
|
||||
|
|
||||
{% block scripts %} |
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog"> |
||||
{{ window_vars(user) }} |
<q-card class="q-pa-lg q-pt-xl" style="width: 500px;"> |
||||
<script> |
<q-form @submit="createTPoS" class="q-gutter-md"> |
||||
var mapTPoS = function (obj) { |
<q-input |
||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); |
filled |
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount); |
dense |
||||
obj.tpos = ['/tpos/', obj.id].join(''); |
v-model.trim="formDialog.data.name" |
||||
return obj; |
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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin], |
mixins: [windowMixin], |
||||
data: function () { |
data: function () { |
||||
return { |
return { |
||||
tposs: [], |
tposs: [], |
||||
currencyOptions: [ |
currencyOptions: [ |
||||
'USD','EUR','GBP','AED','AFN','ALL','AMD','ANG','AOA','ARS','AUD','AWG','AZN','BAM','BBD','BDT','BGN','BHD', |
'USD', |
||||
'BIF','BMD','BND','BOB','BRL','BSD','BTN','BWP','BYN','BZD','CAD','CDF','CHF','CLF','CLP','CNH','CNY','COP', |
'EUR', |
||||
'CRC','CUC','CUP','CVE','CZK','DJF','DKK','DOP','DZD','EGP','ERN','ETB','EUR','FJD','FKP','GBP','GEL','GGP', |
'GBP', |
||||
'GHS','GIP','GMD','GNF','GTQ','GYD','HKD','HNL','HRK','HTG','HUF','IDR','ILS','IMP','INR','IQD','IRR','ISK', |
'AED', |
||||
'JEP','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL', |
'AFN', |
||||
'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', |
'ALL', |
||||
'SCR','SDG','SEK','SGD','SHP','SLL','SOS','SRD','SSP','STD','SVC','SYP','SZL','THB','TJS','TMT','TND','TOP', |
'AMD', |
||||
'TRY','TTD','TWD','TZS','UAH','UGX','USD','UYU','UZS','VEF','VES','VND','VUV','WST','XAF','XAG','XAU','XCD', |
'ANG', |
||||
'XDR','XOF','XPD','XPF','XPT','YER','ZAR','ZMW','ZWL' |
'AOA', |
||||
], |
'ARS', |
||||
tpossTable: { |
'AUD', |
||||
columns: [ |
'AWG', |
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
'AZN', |
||||
{name: 'name', align: 'left', label: 'Name', field: 'name'}, |
'BAM', |
||||
{name: 'currency', align: 'left', label: 'Currency', field: 'currency'} |
'BBD', |
||||
], |
'BDT', |
||||
pagination: { |
'BGN', |
||||
rowsPerPage: 10 |
'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: { |
pagination: { |
||||
show: false, |
rowsPerPage: 10 |
||||
data: {} |
|
||||
} |
} |
||||
}; |
|
||||
}, |
|
||||
methods: { |
|
||||
closeFormDialog: function () { |
|
||||
this.formDialog.data = {}; |
|
||||
}, |
}, |
||||
getTPoSs: function () { |
formDialog: { |
||||
var self = this; |
show: false, |
||||
|
data: {} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
closeFormDialog: function () { |
||||
|
this.formDialog.data = {} |
||||
|
}, |
||||
|
getTPoSs: function () { |
||||
|
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'GET', |
'GET', |
||||
'/tpos/api/v1/tposs?all_wallets', |
'/tpos/api/v1/tposs?all_wallets', |
||||
this.g.user.wallets[0].inkey |
this.g.user.wallets[0].inkey |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
self.tposs = response.data.map(function (obj) { |
self.tposs = response.data.map(function (obj) { |
||||
return mapTPoS(obj); |
return mapTPoS(obj) |
||||
}); |
}) |
||||
}); |
}) |
||||
}, |
}, |
||||
createTPoS: function () { |
createTPoS: function () { |
||||
var data = { |
var data = { |
||||
name: this.formDialog.data.name, |
name: this.formDialog.data.name, |
||||
currency: this.formDialog.data.currency |
currency: this.formDialog.data.currency |
||||
}; |
} |
||||
var self = this; |
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'POST', |
'POST', |
||||
'/tpos/api/v1/tposs', |
'/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 |
data |
||||
).then(function (response) { |
) |
||||
self.tposs.push(mapTPoS(response.data)); |
.then(function (response) { |
||||
self.formDialog.show = false; |
self.tposs.push(mapTPoS(response.data)) |
||||
}).catch(function (error) { |
self.formDialog.show = false |
||||
LNbits.utils.notifyApiError(error); |
}) |
||||
}); |
.catch(function (error) { |
||||
}, |
LNbits.utils.notifyApiError(error) |
||||
deleteTPoS: function (tposId) { |
}) |
||||
var self = this; |
}, |
||||
var tpos = _.findWhere(this.tposs, {id: tposId}); |
deleteTPoS: function (tposId) { |
||||
|
var self = this |
||||
|
var tpos = _.findWhere(this.tposs, {id: tposId}) |
||||
|
|
||||
LNbits.utils.confirmDialog( |
LNbits.utils |
||||
'Are you sure you want to delete this TPoS?' |
.confirmDialog('Are you sure you want to delete this TPoS?') |
||||
).onOk(function () { |
.onOk(function () { |
||||
LNbits.api.request( |
LNbits.api |
||||
'DELETE', |
.request( |
||||
'/tpos/api/v1/tposs/' + tposId, |
'DELETE', |
||||
_.findWhere(self.g.user.wallets, {id: tpos.wallet}).adminkey |
'/tpos/api/v1/tposs/' + tposId, |
||||
).then(function (response) { |
_.findWhere(self.g.user.wallets, {id: tpos.wallet}).adminkey |
||||
self.tposs = _.reject(self.tposs, function (obj) { return obj.id == tposId; }); |
) |
||||
}).catch(function (error) { |
.then(function (response) { |
||||
LNbits.utils.notifyApiError(error); |
self.tposs = _.reject(self.tposs, function (obj) { |
||||
}); |
return obj.id == tposId |
||||
}); |
}) |
||||
}, |
}) |
||||
exportCSV: function () { |
.catch(function (error) { |
||||
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs); |
LNbits.utils.notifyApiError(error) |
||||
} |
}) |
||||
|
}) |
||||
}, |
}, |
||||
created: function () { |
exportCSV: function () { |
||||
if (this.g.user.wallets.length) { |
LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs) |
||||
this.getTPoSs(); |
} |
||||
} |
}, |
||||
|
created: function () { |
||||
|
if (this.g.user.wallets.length) { |
||||
|
this.getTPoSs() |
||||
} |
} |
||||
}); |
} |
||||
</script> |
}) |
||||
|
</script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -1,225 +1,265 @@ |
|||||
{% extends "public.html" %} |
{% extends "public.html" %} {% block toolbar_title %}{{ tpos.name }}{% endblock |
||||
|
%} {% block footer %}{% endblock %} {% block page_container %} |
||||
|
<q-page-container> |
||||
{% block toolbar_title %}{{ tpos.name }}{% endblock %} |
<q-page> |
||||
|
<q-page-sticky v-if="exchangeRate" expand position="top"> |
||||
{% block footer %}{% endblock %} |
<div class="row justify-center full-width"> |
||||
|
<div class="col-12 col-sm-8 col-md-6 col-lg-4 text-center"> |
||||
{% block page_container %} |
<h3 class="q-mb-md">{% raw %}{{ famount }}{% endraw %}</h3> |
||||
<q-page-container> |
<h5 class="q-mt-none"> |
||||
<q-page> |
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
||||
<q-page-sticky v-if="exchangeRate" expand position="top"> |
</h5> |
||||
<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> |
|
||||
</div> |
</div> |
||||
</q-page-sticky> |
</div> |
||||
<q-page-sticky expand position="bottom"> |
</q-page-sticky> |
||||
<div class="row justify-center full-width"> |
<q-page-sticky expand position="bottom"> |
||||
<div class="col-12 col-sm-8 col-md-6 col-lg-4"> |
<div class="row justify-center full-width"> |
||||
<div class="keypad q-pa-sm"> |
<div class="col-12 col-sm-8 col-md-6 col-lg-4"> |
||||
<q-btn unelevated |
<div class="keypad q-pa-sm"> |
||||
@click="stack.push(1)" |
<q-btn unelevated @click="stack.push(1)" size="xl" color="grey-8" |
||||
size="xl" color="grey-8">1</q-btn> |
>1</q-btn |
||||
<q-btn unelevated |
> |
||||
@click="stack.push(2)" |
<q-btn unelevated @click="stack.push(2)" size="xl" color="grey-8" |
||||
size="xl" color="grey-8">2</q-btn> |
>2</q-btn |
||||
<q-btn unelevated |
> |
||||
@click="stack.push(3)" |
<q-btn unelevated @click="stack.push(3)" size="xl" color="grey-8" |
||||
size="xl" color="grey-8">3</q-btn> |
>3</q-btn |
||||
<q-btn unelevated |
> |
||||
@click="stack = []" |
<q-btn |
||||
size="xl" color="pink" class="btn-cancel">C</q-btn> |
unelevated |
||||
<q-btn unelevated |
@click="stack = []" |
||||
@click="stack.push(4)" |
size="xl" |
||||
size="xl" color="grey-8">4</q-btn> |
color="pink" |
||||
<q-btn unelevated |
class="btn-cancel" |
||||
@click="stack.push(5)" |
>C</q-btn |
||||
size="xl" color="grey-8">5</q-btn> |
> |
||||
<q-btn unelevated |
<q-btn unelevated @click="stack.push(4)" size="xl" color="grey-8" |
||||
@click="stack.push(6)" |
>4</q-btn |
||||
size="xl" color="grey-8">6</q-btn> |
> |
||||
<q-btn unelevated |
<q-btn unelevated @click="stack.push(5)" size="xl" color="grey-8" |
||||
@click="stack.push(7)" |
>5</q-btn |
||||
size="xl" color="grey-8">7</q-btn> |
> |
||||
<q-btn unelevated |
<q-btn unelevated @click="stack.push(6)" size="xl" color="grey-8" |
||||
@click="stack.push(8)" |
>6</q-btn |
||||
size="xl" color="grey-8">8</q-btn> |
> |
||||
<q-btn unelevated |
<q-btn unelevated @click="stack.push(7)" size="xl" color="grey-8" |
||||
@click="stack.push(9)" |
>7</q-btn |
||||
size="xl" color="grey-8">9</q-btn> |
> |
||||
<q-btn unelevated |
<q-btn unelevated @click="stack.push(8)" size="xl" color="grey-8" |
||||
:disabled="amount == 0" |
>8</q-btn |
||||
@click="showInvoice()" |
> |
||||
size="xl" color="green" class="btn-confirm">OK</q-btn> |
<q-btn unelevated @click="stack.push(9)" size="xl" color="grey-8" |
||||
<q-btn unelevated |
>9</q-btn |
||||
@click="stack.splice(-1, 1)" |
> |
||||
size="xl" color="grey-7">DEL</q-btn> |
<q-btn |
||||
<q-btn unelevated |
unelevated |
||||
@click="stack.push(0)" |
:disabled="amount == 0" |
||||
size="xl" color="grey-8">0</q-btn> |
@click="showInvoice()" |
||||
<q-btn unelevated |
size="xl" |
||||
@click="urlDialog.show = true" |
color="green" |
||||
size="xl" color="grey-7">#</q-btn> |
class="btn-confirm" |
||||
</div> |
>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> |
||||
</div> |
</div> |
||||
</q-page-sticky> |
</div> |
||||
<q-dialog v-model="invoiceDialog.show" position="top" @hide="closeInvoiceDialog"> |
</q-page-sticky> |
||||
<q-card v-if="invoiceDialog.data" class="q-pa-lg q-pt-xl lnbits__dialog-card"> |
<q-dialog |
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
v-model="invoiceDialog.show" |
||||
<qrcode :value="invoiceDialog.data.payment_request" :options="{width: 800}" class="rounded-borders"></qrcode> |
position="top" |
||||
</q-responsive> |
@hide="closeInvoiceDialog" |
||||
<div class="text-center"> |
> |
||||
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3> |
<q-card |
||||
<h5 class="q-mt-none"> |
v-if="invoiceDialog.data" |
||||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
class="q-pa-lg q-pt-xl lnbits__dialog-card" |
||||
</h5> |
> |
||||
</div> |
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
||||
<div class="row q-mt-lg"> |
<qrcode |
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
:value="invoiceDialog.data.payment_request" |
||||
</div> |
:options="{width: 800}" |
||||
</q-card> |
class="rounded-borders" |
||||
</q-dialog> |
></qrcode> |
||||
<q-dialog v-model="urlDialog.show" position="top"> |
</q-responsive> |
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> |
<div class="text-center"> |
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3> |
||||
<qrcode value="{{ request.url }}" :options="{width: 800}" class="rounded-borders"></qrcode> |
<h5 class="q-mt-none"> |
||||
</q-responsive> |
{% raw %}{{ fsat }}{% endraw %} <small>sat</small> |
||||
<div class="text-center q-mb-xl"> |
</h5> |
||||
<p style="word-break: break-all"><strong>{{ tpos.name }}</strong><br>{{ request.url }}</p> |
</div> |
||||
</div> |
<div class="row q-mt-lg"> |
||||
<div class="row q-mt-lg"> |
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
||||
<q-btn outline color="grey" @click="copyText('{{ request.url }}', 'TPoS URL copied to clipboard!')">Copy URL</q-btn> |
</div> |
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
</q-card> |
||||
</div> |
</q-dialog> |
||||
</q-card> |
<q-dialog v-model="urlDialog.show" position="top"> |
||||
</q-dialog> |
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> |
||||
</q-page> |
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> |
||||
</q-page-container> |
<qrcode |
||||
{% endblock %} |
value="{{ request.url }}" |
||||
|
:options="{width: 800}" |
||||
{% block styles %} |
class="rounded-borders" |
||||
<style> |
></qrcode> |
||||
.keypad { |
</q-responsive> |
||||
display: grid; |
<div class="text-center q-mb-xl"> |
||||
grid-gap: 8px; |
<p style="word-break: break-all;"> |
||||
grid-template-columns: repeat(4, 1fr); |
<strong>{{ tpos.name }}</strong><br />{{ request.url }} |
||||
grid-template-rows: repeat(4, 1fr); |
</p> |
||||
} |
</div> |
||||
.keypad .btn { |
<div class="row q-mt-lg"> |
||||
height: 100%; |
<q-btn |
||||
} |
outline |
||||
.btn-cancel, .btn-confirm { |
color="grey" |
||||
grid-row: auto/span 2; |
@click="copyText('{{ request.url }}', 'TPoS URL copied to clipboard!')" |
||||
} |
>Copy URL</q-btn |
||||
</style> |
> |
||||
{% endblock %} |
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> |
||||
|
</div> |
||||
{% block scripts %} |
</q-card> |
||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> |
</q-dialog> |
||||
<script> |
</q-page> |
||||
Vue.component(VueQrcode.name, VueQrcode); |
</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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin], |
mixins: [windowMixin], |
||||
data: function () { |
data: function () { |
||||
return { |
return { |
||||
tposId: '{{ tpos.id }}', |
tposId: '{{ tpos.id }}', |
||||
currency: '{{ tpos.currency }}', |
currency: '{{ tpos.currency }}', |
||||
exchangeRate: null, |
exchangeRate: null, |
||||
stack: [], |
stack: [], |
||||
invoiceDialog: { |
invoiceDialog: { |
||||
show: false, |
show: false, |
||||
data: null, |
data: null, |
||||
dismissMsg: null, |
dismissMsg: null, |
||||
paymentChecker: null |
paymentChecker: null |
||||
}, |
|
||||
urlDialog: { |
|
||||
show: false |
|
||||
} |
|
||||
}; |
|
||||
}, |
|
||||
computed: { |
|
||||
amount: function () { |
|
||||
if (!this.stack.length) return 0.00; |
|
||||
return (Number(this.stack.join('')) / 100).toFixed(2); |
|
||||
}, |
}, |
||||
famount: function () { |
urlDialog: { |
||||
return LNbits.utils.formatCurrency(this.amount, this.currency); |
show: false |
||||
}, |
|
||||
sat: function () { |
|
||||
if (!this.exchangeRate) return 0; |
|
||||
return Math.ceil((this.amount / this.exchangeRate) * 100000000); |
|
||||
}, |
|
||||
fsat: function () { |
|
||||
return LNbits.utils.formatSat(this.sat); |
|
||||
} |
} |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
amount: function () { |
||||
|
if (!this.stack.length) return 0.0 |
||||
|
return (Number(this.stack.join('')) / 100).toFixed(2) |
||||
}, |
}, |
||||
methods: { |
famount: function () { |
||||
closeInvoiceDialog: function () { |
return LNbits.utils.formatCurrency(this.amount, this.currency) |
||||
this.stack = []; |
}, |
||||
var dialog = this.invoiceDialog; |
sat: function () { |
||||
setTimeout(function () { |
if (!this.exchangeRate) return 0 |
||||
clearInterval(dialog.paymentChecker); |
return Math.ceil((this.amount / this.exchangeRate) * 100000000) |
||||
dialog.dismissMsg(); |
}, |
||||
}, 3000); |
fsat: function () { |
||||
}, |
return LNbits.utils.formatSat(this.sat) |
||||
showInvoice: function () { |
} |
||||
var self = this; |
}, |
||||
var dialog = this.invoiceDialog; |
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( |
axios |
||||
'/tpos/api/v1/tposs/' + this.tposId + '/invoices/', |
.post('/tpos/api/v1/tposs/' + this.tposId + '/invoices/', { |
||||
{amount: this.sat} |
amount: this.sat |
||||
).then(function (response) { |
}) |
||||
dialog.data = response.data; |
.then(function (response) { |
||||
dialog.show = true; |
dialog.data = response.data |
||||
|
dialog.show = true |
||||
|
|
||||
dialog.dismissMsg = self.$q.notify({ |
dialog.dismissMsg = self.$q.notify({ |
||||
timeout: 0, |
timeout: 0, |
||||
message: 'Waiting for payment...' |
message: 'Waiting for payment...' |
||||
}); |
}) |
||||
|
|
||||
dialog.paymentChecker = setInterval(function () { |
dialog.paymentChecker = setInterval(function () { |
||||
axios.get( |
axios |
||||
'/tpos/api/v1/tposs/' + self.tposId + '/invoices/' + response.data.checking_id |
.get( |
||||
).then(function (res) { |
'/tpos/api/v1/tposs/' + |
||||
if (res.data.paid) { |
self.tposId + |
||||
clearInterval(dialog.paymentChecker); |
'/invoices/' + |
||||
dialog.dismissMsg(); |
response.data.checking_id |
||||
dialog.show = false; |
) |
||||
|
.then(function (res) { |
||||
|
if (res.data.paid) { |
||||
|
clearInterval(dialog.paymentChecker) |
||||
|
dialog.dismissMsg() |
||||
|
dialog.show = false |
||||
|
|
||||
self.$q.notify({ |
self.$q.notify({ |
||||
type: 'positive', |
type: 'positive', |
||||
message: self.fsat + ' sat received!', |
message: self.fsat + ' sat received!', |
||||
icon: null |
icon: null |
||||
}); |
}) |
||||
} |
} |
||||
}); |
}) |
||||
}, 2000); |
}, 2000) |
||||
|
}) |
||||
}).catch(function (error) { |
.catch(function (error) { |
||||
LNbits.utils.notifyApiError(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]; |
|
||||
}); |
|
||||
} |
|
||||
}, |
}, |
||||
created: function () { |
getRates: function () { |
||||
var getRates = this.getRates; |
var self = this |
||||
getRates(); |
axios.get('https://api.opennode.co/v1/rates').then(function (response) { |
||||
setInterval(function () { getRates(); }, 20000); |
self.exchangeRate = |
||||
|
response.data.data['BTC' + self.currency][self.currency] |
||||
|
}) |
||||
} |
} |
||||
}); |
}, |
||||
</script> |
created: function () { |
||||
|
var getRates = this.getRates |
||||
|
getRates() |
||||
|
setInterval(function () { |
||||
|
getRates() |
||||
|
}, 20000) |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -1,416 +1,453 @@ |
|||||
{% extends "base.html" %} |
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context |
||||
|
%} {% block page %} |
||||
{% from "macros.jinja" import window_vars with context %} |
<div class="row q-col-gutter-md"> |
||||
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
||||
|
<q-card> |
||||
{% block page %} |
<q-card-section> |
||||
<div class="row q-col-gutter-md"> |
<q-btn unelevated color="deep-purple" @click="userDialog.show = true" |
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
>New User</q-btn |
||||
<q-card> |
> |
||||
<q-card-section> |
<q-btn unelevated color="deep-purple" @click="walletDialog.show = true" |
||||
<q-btn unelevated color="deep-purple" @click="userDialog.show = true">New User</q-btn> |
>New Wallet |
||||
<q-btn unelevated color="deep-purple" @click="walletDialog.show = true">New Wallet |
</q-btn> |
||||
</q-btn> |
</q-card-section> |
||||
</q-card-section> |
</q-card> |
||||
</q-card> |
|
||||
|
<q-card> |
||||
<q-card> |
<q-card-section> |
||||
<q-card-section> |
<div class="row items-center no-wrap q-mb-md"> |
||||
<div class="row items-center no-wrap q-mb-md"> |
<div class="col"> |
||||
<div class="col"> |
<h5 class="text-subtitle1 q-my-none">Users</h5> |
||||
<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> |
|
||||
</div> |
</div> |
||||
<q-table dense flat |
<div class="col-auto"> |
||||
:data="users" |
<q-btn flat color="grey" @click="exportUsersCSV" |
||||
row-key="id" |
>Export to CSV</q-btn |
||||
: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> |
</div> |
||||
<q-table dense flat |
</div> |
||||
:data="wallets" |
<q-table |
||||
row-key="id" |
dense |
||||
:columns="walletsTable.columns" |
flat |
||||
:pagination.sync="walletsTable.pagination"> |
:data="users" |
||||
{% raw %} |
row-key="id" |
||||
<template v-slot:header="props"> |
:columns="usersTable.columns" |
||||
<q-tr :props="props"> |
:pagination.sync="usersTable.pagination" |
||||
<q-th auto-width></q-th> |
> |
||||
<q-th |
{% raw %} |
||||
v-for="col in props.cols" |
<template v-slot:header="props"> |
||||
:key="col.name" |
<q-tr :props="props"> |
||||
:props="props" |
<q-th v-for="col in props.cols" :key="col.name" :props="props"> |
||||
> |
{{ col.label }} |
||||
{{ col.label }} |
</q-th> |
||||
</q-th> |
<q-th auto-width></q-th> |
||||
<q-th auto-width></q-th> |
</q-tr> |
||||
</q-tr> |
</template> |
||||
</template> |
<template v-slot:body="props"> |
||||
<template v-slot:body="props"> |
<q-tr :props="props"> |
||||
<q-tr :props="props"> |
<q-td v-for="col in props.cols" :key="col.name" :props="props"> |
||||
<q-td auto-width> |
{{ col.value }} |
||||
<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-td> |
||||
<q-tooltip> |
<q-td auto-width> |
||||
Link to wallet |
<q-btn |
||||
</q-tooltip> |
flat |
||||
|
dense |
||||
</q-td> |
size="xs" |
||||
<q-td |
@click="deleteUser(props.row.id)" |
||||
v-for="col in props.cols" |
icon="cancel" |
||||
:key="col.name" |
color="pink" |
||||
:props="props" |
></q-btn> |
||||
> |
</q-td> |
||||
|
</q-tr> |
||||
{{ col.value }} |
</template> |
||||
</q-td> |
{% endraw %} |
||||
<q-td auto-width> |
</q-table> |
||||
|
</q-card-section> |
||||
<q-btn flat dense size="xs" @click="deleteWallet(props.row.id)" icon="cancel" color="pink"></q-btn> |
</q-card> |
||||
</q-td> |
|
||||
</q-tr> |
<q-card> |
||||
</template> |
<q-card-section> |
||||
{% endraw %} |
<div class="row items-center no-wrap q-mb-md"> |
||||
</q-table> |
<div class="col"> |
||||
</q-card-section> |
<h5 class="text-subtitle1 q-my-none">Wallets</h5> |
||||
</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> |
</div> |
||||
</q-form> |
<div class="col-auto"> |
||||
</q-card> |
<q-btn flat color="grey" @click="exportWalletsCSV" |
||||
</q-dialog> |
>Export to CSV</q-btn |
||||
|
> |
||||
|
|
||||
|
|
||||
<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> |
</div> |
||||
</q-form> |
</div> |
||||
</q-card> |
<q-table |
||||
</q-dialog> |
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> |
||||
{% 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', |
'GET', |
||||
'/usermanager/api/v1/users', |
'/usermanager/api/v1/users', |
||||
this.g.user.wallets[0].inkey |
this.g.user.wallets[0].inkey |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
|
|
||||
self.users = response.data.map(function (obj) { |
self.users = response.data.map(function (obj) { |
||||
|
return mapUserManager(obj) |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
return mapUserManager(obj); |
openUserUpdateDialog: function (linkId) { |
||||
}); |
var link = _.findWhere(this.users, {id: linkId}) |
||||
|
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
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{ |
|
||||
|
|
||||
|
this.userDialog.data = _.clone(link._data) |
||||
|
this.userDialog.show = true |
||||
|
}, |
||||
|
sendUserFormData: function () { |
||||
|
if (this.userDialog.data.id) { |
||||
|
} else { |
||||
var data = { |
var data = { |
||||
|
|
||||
admin_id: this.g.user.id, |
admin_id: this.g.user.id, |
||||
user_name: this.userDialog.data.usrname, |
user_name: this.userDialog.data.usrname, |
||||
wallet_name: this.userDialog.data.walname |
wallet_name: this.userDialog.data.walname |
||||
};} |
} |
||||
|
} |
||||
|
|
||||
{ this.createUser(data); } |
{ |
||||
}, |
this.createUser(data) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
createUser: function (data) { |
createUser: function (data) { |
||||
var self = this; |
var self = this |
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'POST', |
'POST', |
||||
'/usermanager/api/v1/users', |
'/usermanager/api/v1/users', |
||||
this.g.user.wallets[0].inkey, |
this.g.user.wallets[0].inkey, |
||||
data |
data |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
self.users.push(mapUserManager(response.data)); |
self.users.push(mapUserManager(response.data)) |
||||
self.userDialog.show = false; |
self.userDialog.show = false |
||||
self.userDialog.data = {}; |
self.userDialog.data = {} |
||||
data = {}; |
data = {} |
||||
}).catch(function (error) { |
}) |
||||
LNbits.utils.notifyApiError(error); |
.catch(function (error) { |
||||
}); |
LNbits.utils.notifyApiError(error) |
||||
}, |
}) |
||||
deleteUser: function (userId) { |
}, |
||||
var self = this; |
deleteUser: function (userId) { |
||||
|
var self = this |
||||
console.log(userId) |
|
||||
LNbits.utils.confirmDialog( |
console.log(userId) |
||||
'Are you sure you want to delete this User link?' |
LNbits.utils |
||||
).onOk(function () { |
.confirmDialog('Are you sure you want to delete this User link?') |
||||
|
.onOk(function () { |
||||
LNbits.api.request( |
LNbits.api |
||||
'DELETE', |
.request( |
||||
'/usermanager/api/v1/users/' + userId, |
'DELETE', |
||||
self.g.user.wallets[0].inkey |
'/usermanager/api/v1/users/' + userId, |
||||
).then(function (response) { |
self.g.user.wallets[0].inkey |
||||
self.users = _.reject(self.users, function (obj) { return obj.id == userId; }); |
) |
||||
}).catch(function (error) { |
.then(function (response) { |
||||
LNbits.utils.notifyApiError(error); |
self.users = _.reject(self.users, function (obj) { |
||||
}); |
return obj.id == userId |
||||
}); |
}) |
||||
}, |
}) |
||||
|
.catch(function (error) { |
||||
exportUsersCSV: function () { |
LNbits.utils.notifyApiError(error) |
||||
LNbits.utils.exportCSV(this.usersTable.columns, this.users); |
}) |
||||
}, |
}) |
||||
|
}, |
||||
|
|
||||
|
exportUsersCSV: function () { |
||||
|
LNbits.utils.exportCSV(this.usersTable.columns, this.users) |
||||
|
}, |
||||
|
|
||||
///////////////Wallets//////////////////////////// |
///////////////Wallets//////////////////////////// |
||||
|
|
||||
getWallets: function () { |
getWallets: function () { |
||||
var self = this; |
var self = this |
||||
|
|
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'GET', |
'GET', |
||||
'/usermanager/api/v1/wallets', |
'/usermanager/api/v1/wallets', |
||||
this.g.user.wallets[0].inkey |
this.g.user.wallets[0].inkey |
||||
).then(function (response) { |
) |
||||
|
.then(function (response) { |
||||
self.wallets = response.data.map(function (obj) { |
self.wallets = response.data.map(function (obj) { |
||||
return mapUserManager(obj); |
return mapUserManager(obj) |
||||
}); |
}) |
||||
}); |
}) |
||||
}, |
}, |
||||
openWalletUpdateDialog: function (linkId) { |
openWalletUpdateDialog: function (linkId) { |
||||
var link = _.findWhere(this.users, {id: linkId}); |
var link = _.findWhere(this.users, {id: linkId}) |
||||
|
|
||||
this.walletDialog.data = _.clone(link._data); |
this.walletDialog.data = _.clone(link._data) |
||||
this.walletDialog.show = true; |
this.walletDialog.show = true |
||||
}, |
}, |
||||
sendWalletFormData: function () { |
sendWalletFormData: function () { |
||||
if (this.walletDialog.data.id){} |
if (this.walletDialog.data.id) { |
||||
else{ |
} else { |
||||
var data = { |
var data = { |
||||
user_id: this.walletDialog.data.user, |
user_id: this.walletDialog.data.user, |
||||
admin_id: this.g.user.id, |
admin_id: this.g.user.id, |
||||
wallet_name: this.walletDialog.data.walname |
wallet_name: this.walletDialog.data.walname |
||||
};} |
} |
||||
|
} |
||||
|
|
||||
{ this.createWallet(data); } |
{ |
||||
}, |
this.createWallet(data) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
createWallet: function (data) { |
createWallet: function (data) { |
||||
var self = this; |
var self = this |
||||
LNbits.api.request( |
LNbits.api |
||||
|
.request( |
||||
'POST', |
'POST', |
||||
'/usermanager/api/v1/wallets', |
'/usermanager/api/v1/wallets', |
||||
this.g.user.wallets[0].inkey, |
this.g.user.wallets[0].inkey, |
||||
data |
data |
||||
).then(function (response) { |
) |
||||
self.wallets.push(mapUserManager(response.data)); |
.then(function (response) { |
||||
self.walletDialog.show = false; |
self.wallets.push(mapUserManager(response.data)) |
||||
self.walletDialog.data = {}; |
self.walletDialog.show = false |
||||
data = {}; |
self.walletDialog.data = {} |
||||
}).catch(function (error) { |
data = {} |
||||
LNbits.utils.notifyApiError(error); |
}) |
||||
}); |
.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); |
|
||||
} |
|
||||
|
|
||||
}, |
}, |
||||
created: function () { |
deleteWallet: function (userId) { |
||||
if (this.g.user.wallets.length) { |
var self = this |
||||
this.getUsers(); |
|
||||
this.getWallets(); |
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) |
||||
} |
} |
||||
}); |
}, |
||||
|
created: function () { |
||||
</script> |
if (this.g.user.wallets.length) { |
||||
|
this.getUsers() |
||||
|
this.getWallets() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -1,12 +1,29 @@ |
|||||
<q-expansion-item |
<q-expansion-item group="extras" icon="info" label="Powered by LNURL"> |
||||
group="extras" |
|
||||
icon="info" |
|
||||
label="Powered by LNURL"> |
|
||||
<q-card> |
<q-card> |
||||
<q-card-section> |
<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> |
||||
<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> |
<b>WARNING: LNURL must be used over https or TOR</b><br /> |
||||
<small>Check <a href="https://github.com/fiatjaf/awesome-lnurl" target="_blank">Awesome LNURL</a> for further information.</small> |
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-section> |
||||
</q-card> |
</q-card> |
||||
</q-expansion-item> |
</q-expansion-item> |
||||
|
@ -1,52 +1,57 @@ |
|||||
{% extends "public.html" %} |
{% 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"> |
||||
{% block page %} |
<q-card class="q-pa-lg"> |
||||
<div class="row q-col-gutter-md justify-center"> |
<q-card-section class="q-pa-none"> |
||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4"> |
<div class="text-center"> |
||||
<q-card class="q-pa-lg"> |
{% if link.is_spent %} |
||||
<q-card-section class="q-pa-none"> |
<q-badge color="red" class="q-mb-md">Withdraw is spent.</q-badge> |
||||
<div class="text-center"> |
{% endif %} |
||||
{% if link.is_spent %} |
<a href="lightning:{{ link.lnurl }}"> |
||||
<q-badge color="red" class="q-mb-md">Withdraw is spent.</q-badge> |
<q-responsive :ratio="1" class="q-mx-md"> |
||||
{% endif %} |
<qrcode |
||||
<a href="lightning:{{ link.lnurl }}"> |
value="{{ link.lnurl }}" |
||||
<q-responsive :ratio="1" class="q-mx-md"> |
:options="{width: 800}" |
||||
<qrcode value="{{ link.lnurl }}" :options="{width: 800}" class="rounded-borders"></qrcode> |
class="rounded-borders" |
||||
</q-responsive> |
></qrcode> |
||||
</a> |
</q-responsive> |
||||
</div> |
</a> |
||||
<div class="row q-mt-lg"> |
</div> |
||||
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')">Copy LNURL</q-btn> |
<div class="row q-mt-lg"> |
||||
</div> |
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')" |
||||
</q-card-section> |
>Copy LNURL</q-btn |
||||
</q-card> |
> |
||||
</div> |
</div> |
||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md"> |
</q-card-section> |
||||
<q-card> |
</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> |
</div> |
||||
{% endblock %} |
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md"> |
||||
|
<q-card> |
||||
{% block scripts %} |
<q-card-section> |
||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> |
<h6 class="text-subtitle1 q-mb-sm q-mt-none"> |
||||
<script> |
LNbits LNURL-withdraw link |
||||
Vue.component(VueQrcode.name, VueQrcode); |
</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({ |
new Vue({ |
||||
el: '#vue', |
el: '#vue', |
||||
mixins: [windowMixin] |
mixins: [windowMixin] |
||||
}); |
}) |
||||
</script> |
</script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -1,66 +1,59 @@ |
|||||
{% extends "print.html" %} |
{% extends "print.html" %} {% block page %} |
||||
|
<div class="row justify-center"> |
||||
|
<div class="col-12 col-sm-8 col-lg-6 text-center"> |
||||
{% block page %} |
{% for i in range(link.uses) %} |
||||
<div class="row justify-center"> |
<div class="zimbabwe"> |
||||
<div class="col-12 col-sm-8 col-lg-6 text-center"> |
<div class="qr"> |
||||
{% for i in range(link.uses) %} |
<qrcode value="{{ link.lnurl }}" :options="{width: 150}"></qrcode> |
||||
<div class="zimbabwe"> |
<br /><br /> |
||||
<div class="qr"> |
<strong>{{ SITE_TITLE }}</strong><br /> |
||||
<qrcode value="{{ link.lnurl }}" :options="{width: 150}"></qrcode> |
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br /> |
||||
<br><br> |
<small>Scan and follow link<br />or use Lightning wallet</small> |
||||
<strong>{{ SITE_TITLE }}</strong><br> |
</div> |
||||
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br> |
<img src="{{ url_for('static', filename='images/note.jpg') }}" /> |
||||
<small>Scan and follow link<br>or use Lightning wallet</small> |
|
||||
</div> |
|
||||
<img src="{{ url_for('static', filename='images/note.jpg') }}"> |
|
||||
</div> |
|
||||
{% endfor %} |
|
||||
</div> |
</div> |
||||
|
{% endfor %} |
||||
</div> |
</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 %} |
new Vue({ |
||||
<style> |
el: '#vue', |
||||
.zimbabwe { |
created: function () { |
||||
page-break-inside: avoid; |
window.print() |
||||
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; |
</script> |
||||
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> |
|
||||
{% endblock %} |
{% endblock %} |
||||
|
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"devDependencies": { |
||||
|
"prettier": "^2.0.5" |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue