satmachineclient/templates/temp/tpos.html
2023-12-08 20:39:27 +00:00

1205 lines
34 KiB
HTML

{% extends "public.html" %} {% block toolbar_title %} {{ temp.name }}
<q-btn
flat
dense
size="md"
@click.prevent="urlDialog.show = true"
icon="share"
color="white"
></q-btn>
<q-btn-toggle
v-model="monochrome"
toggle-color="primary"
:options="[
{label: 'Color', value: false},
{label: 'Mono', value: true}
]"
size="sm"
@input="handleColorScheme"
class="q-ml-md"
/>
{% endblock %} {% block footer %}{% endblock %} {% block page_container %}
<q-page-container>
<q-page>
<q-page-sticky v-if="exchangeRate && showPoS" expand position="top">
<div class="row justify-center full-width">
<div class="col-12 col-sm-8 col-md-6 col-lg-4 text-center">
<h3 class="q-mb-md">${ amountFormatted }</h3>
<h5 class="q-mt-none q-mb-sm">${ fsat }<small> sat</small></h5>
<div v-if="total > 0.0">
<h5>
<i>Total: ${totalFormatted}<br />${totalfsat} sat</i>
</h5>
</div>
</div>
</div>
</q-page-sticky>
<temp v-if="showPoS">
<q-page-sticky expand position="bottom">
<div class="row justify-center full-width">
<div class="col-12 col-sm-8 col-md-6 col-lg-4">
<div class="keypad q-pa-sm">
<q-btn
unelevated
@click="stack.push(1)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>1</q-btn
>
<q-btn
unelevated
@click="stack.push(2)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>2</q-btn
>
<q-btn
unelevated
@click="stack.push(3)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>3</q-btn
>
<q-btn
unelevated
@click="stack.push(4)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>4</q-btn
>
<q-btn
unelevated
@click="stack.push(5)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>5</q-btn
>
<q-btn
unelevated
@click="stack.push(6)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>6</q-btn
>
<q-btn
unelevated
@click="stack.push(7)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>7</q-btn
>
<q-btn
unelevated
@click="stack.push(8)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>8</q-btn
>
<q-btn
unelevated
@click="stack.push(9)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>9</q-btn
>
<q-btn
unelevated
@click="total > 0.0 ? cancelAddAmount() : stack = []"
size="xl"
:outline="!($q.dark.isActive)"
rounded
:color="monochrome ? 'secondary' : 'negative'"
>C</q-btn
>
<q-btn
unelevated
@click="stack.push(0)"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
>0</q-btn
>
<q-btn
unelevated
@click="stack.pop()"
size="xl"
:outline="!($q.dark.isActive)"
rounded
:color="monochrome ? 'secondary' : 'warning'"
class="btn-cancel"
></q-btn
>
<q-btn
unelevated
@click="addAmount()"
size="xl"
:outline="!($q.dark.isActive)"
rounded
color="primary"
class="btn-plus"
>+</q-btn
>
<q-btn
unelevated
@click="submitForm()"
size="xl"
:outline="!($q.dark.isActive)"
rounded
:color="monochrome ? 'secondary' : 'positive'"
class="btn-confirm"
>Ok</q-btn
>
</div>
</div>
</div>
</q-page-sticky>
<q-page-sticky position="top-right" :offset="[18, 18]">
<q-btn
@click="showLastPayments"
fab
icon="receipt_long"
color="primary"
></q-btn
></q-page-sticky>
<q-page-sticky position="top-right" :offset="[18, 90]">
<q-btn
@click="toggleFullscreen"
fab
:icon="fullScreenIcon"
color="primary"
></q-btn>
</q-page-sticky>
<q-page-sticky position="top-right" :offset="[18, 162]">
<q-btn
@click="atm"
v-if="withdrawamtemps > 0"
fab
icon="atm"
color="primary"
></q-btn>
</q-page-sticky>
<q-page-sticky position="top-right" :offset="[18, 234]">
<q-btn
@click="showPoS = false"
v-if="items.length > 0"
fab
icon="point_of_sale"
color="primary"
:disable="atmMode"
></q-btn>
</q-page-sticky>
</temp>
<temp v-else>
<q-drawer
side="right"
v-model="cartDrawer"
show-if-above
bordered
:width="drawerWidth"
:breakpoint="1200"
>
<div class="row full-width q-pa-md">
<div class="col-12 text-center">
<h3 class="q-mb-md">${totalFormatted}</h3>
<h5 class="q-mt-none q-mb-sm">${totalfsat}<small> sat</small></h5>
</div>
<div class="col-12">
<table class="table full-width">
<colgroup>
<col width="50%" />
<col width="25%" />
<col width="0%" />
<col width="0%" />
</colgroup>
<tbody>
<tr v-for="item in [...cart.values()]" :key="item.id">
<td
class="text-bold ellipsis"
style="
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 1px;
"
>
${item.title}
</td>
<td>
<div
class="flex justify-evenly"
style="align-items: center"
>
<q-btn
@click="removeFromCart(item)"
flat
dense
round
size="sm"
icon="remove"
:disabled="item.quantity == 1"
></q-btn>
<div class="text-center">
<span class="text-bold">${item.quantity}</span>
</div>
<q-btn
@click="addToCart(item)"
flat
dense
round
size="sm"
icon="add"
></q-btn>
</div>
</td>
<td>
<span class="text-center">${item.formattedPrice}</span>
</td>
<td>
<q-btn
@click="removeFromCart(item, item.quantity)"
color="negative"
size="sm"
round
icon="delete"
></q-btn>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row q-col-gutter-md q-pa-md absolute-bottom q-mb-lg">
<div class="col-12 col-sm-6">
<q-btn
class="full-width"
@click="submitForm"
color="positive"
label="Pay"
padding="md"
:disabled="cart.size == 0"
></q-btn>
</div>
<div class="col-12 col-sm-6">
<q-btn
class="full-width"
@click="clearCart"
color="negative"
label="Clear cart"
padding="md"
></q-btn>
</div>
</div>
</q-drawer>
<div class="row justify-center q-col-gutter-md q-pa-md">
<div class="col-12 col-sm-8 col-md-6">
<q-input v-model="searchTerm" label="Filter">
<q-icon
v-if="searchTerm !== ''"
name="close"
@click="searchTerm = ''"
class="cursor-pointer"
/>
</q-input>
</div>
</div>
<div class="row q-col-gutter-md q-pa-md">
<div
class="col-12 col-sm-6 col-md-4 col-lg-3"
v-for="item in filteredItems"
:key="item.name"
>
<q-card class="full-height column">
<img
v-if="item.image"
class="q-pa-md responsive-img"
:src="item.image"
/>
<q-card-section class="q-mb-auto">
<div class="text-h6 ellipsis">${item.title}</div>
<div class="text-subtitle1">${item.formattedPrice}</div>
<div
v-if="item.description"
class="text-caption ellipsis-2-lines q-py-sm"
>
${item.description}
</div>
</q-card-section>
<q-card-actions vertical class="q-pa-md">
<q-btn @click="addToCart(item)" padding="md" color="primary"
>Add</q-btn
>
</q-card-actions>
</q-card>
</div>
</div>
<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-btn
@click="showPoS = true"
fab
icon="dialpad"
color="primary"
></q-btn
></q-page-sticky>
<q-page-sticky position="bottom-right" :offset="[18, 90]">
<q-btn
@click="cartDrawer = !cartDrawer"
fab
:icon="cartDrawer ? 'chevron_right' : 'chevron_left'"
color="primary"
><q-tooltip
>${cartDrawer ? 'Hide Cart' :' Open Cart'}</q-tooltip
></q-btn
></q-page-sticky
>
</temp>
<q-btn
v-show="atmMode"
color="negative"
@click="atmMode = false"
class="absolute-top-left"
>EXIT ATM MODE</q-btn
>
<q-dialog
v-model="invoiceDialog.show"
position="top"
@hide="closeInvoiceDialog"
>
<q-card
v-if="invoiceDialog.data"
class="q-pa-lg q-pt-xl lnbits__dialog-card"
>
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<lnbits-qrcode
:value="'lightning:' + invoiceDialog.data.payment_request.toUpperCase()"
:options="{width: 800}"
class="rounded-borders"
></lnbits-qrcode>
</q-responsive>
<div class="text-center">
<h3 class="q-my-md">${ amountWithTipFormatted }</h3>
<h5 class="q-mt-none">
${ fsat }
<small> sat</small>
<span v-show="tip_options" style="font-size: 0.75rem"
>( + ${ tipAmountFormatted } tip)</span
>
</h5>
<q-btn
outline
color="grey"
icon="nfc"
@click="readNfcTag()"
:disable="nfcTagReading"
></q-btn>
</div>
<div class="row q-mt-lg">
<q-btn
outline
color="grey"
@click="copyText(invoiceDialog.data.payment_request)"
>Copy invoice</q-btn
>
<q-btn
outline
color="grey"
class="q-ml-md"
type="a"
:href="'lightning:'+invoiceDialog.data.payment_request"
>Pay in wallet</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<q-dialog v-model="tipDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-xl">
<b style="font-size: 24px">Would you like to leave a tip?</b>
</div>
<div class="text-center q-mb-xl">
<q-btn
style="padding: 10px; margin: 3px"
unelevated
@click="processTipSelection(tip)"
size="lg"
:outline="!($q.dark.isActive)"
rounded
color="primary"
v-for="tip in tip_options.filter(f => f != 'Round')"
:key="tip"
>${ tip }%</q-btn
>
<q-btn
style="padding: 10px; margin: 3px"
unelevated
@click="setRounding"
size="lg"
:outline="!($q.dark.isActive)"
rounded
color="primary"
label="Round to"
></q-btn>
<div class="row q-my-lg" v-if="rounding">
<q-input
class="col"
ref="inputRounding"
v-model.number="tipRounding"
:placeholder="roundToSugestion"
type="number"
hint="Total amount including tip"
:prefix="currency"
>
</q-input>
<q-btn
class="q-ml-sm"
style="margin-bottom: 20px"
color="primary"
@click="calculatePercent"
>Ok</q-btn
>
</div>
</div>
<div class="row q-mt-lg">
<q-btn flat color="primary" @click="processTipSelection(0)"
>No, thanks</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<q-dialog v-model="urlDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<lnbits-qrcode
value="{{ request.url }}"
:options="{width: 800}"
class="rounded-borders"
></lnbits-qrcode>
</q-responsive>
<div class="text-center q-mb-xl">
<p style="word-break: break-all">
<strong>{{ temp.name }}</strong><br />{{ request.url }}
</p>
</div>
<div class="row q-mt-lg">
<q-btn
outline
color="grey"
@click="copyText('{{ request.url }}', 'Temp URL copied to clipboard!')"
>Copy URL</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<q-dialog v-model="complete.show" position="top">
<q-icon
name="check"
transition-show="fade"
class="text-light-green"
style="font-size: min(90vw, 40em)"
></q-icon>
</q-dialog>
<q-dialog v-model="lastPaymentsDialog.show" position="bottom">
<q-card class="lnbits__dialog-card">
<q-card-section class="row items-center q-pb-none">
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-list separator class="q-mb-lg">
<q-item v-if="!lastPaymentsDialog.data.length">
<q-item-section>
<q-item-label class="text-bold">No paid invoices</q-item-label>
</q-item-section>
</q-item>
<q-item v-for="(payment, idx) in lastPaymentsDialog.data" :key="idx">
<q-item-section>
<q-item-label class="text-bold"
>${payment.amount / 1000} sats</q-item-label
>
<q-item-label caption lines="2"
>Hash: ${payment.checking_id.slice(0, 30)}...</q-item-label
>
</q-item-section>
<q-item-section side top>
<q-item-label caption>${payment.dateFrom}</q-item-label>
<q-icon name="check" color="green" />
</q-item-section>
</q-item>
</q-list>
</q-card>
</q-dialog>
<q-dialog v-model="atmBox" @hide="atmPin = null">
<q-card>
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Withdraw PIN</div>
</q-card-section>
<q-card-section>
<q-form autofocus @submit="atmSubmit" class="q-gutter-md">
<q-input
filled
:type="hidePin ? 'password' : 'number'"
v-model.number="atmPin"
><temp v-slot:append>
<q-icon
:name="hidePin ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="hidePin = !hidePin"
></q-icon> </temp
></q-input>
<div>
<q-btn label="Submit" type="submit" color="primary"></q-btn>
</div>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
</q-page>
</q-page-container>
{% endblock %} {% block styles %}
<style>
* {
touch-action: manipulation;
}
.keypad {
display: grid;
grid-gap: 8px;
grid-temp-columns: repeat(4, 1fr);
grid-temp-rows: repeat(4, 1fr);
min-height: 40vh;
}
.keypad .btn {
height: 100%;
min-height: 56px;
}
.keypad .btn-confirm {
grid-area: 2 / 4 / 5 / 4;
}
.keypad .btn-plus {
grid-area: 1 / 4 / 2 / 4;
}
.itemCard {
height: 100% !important;
display: flex;
}
.responsive-img {
height: 100%;
width: 100%;
max-height: 210px;
object-fit: scale-down;
}
.table {
border-collapse: collapse;
width: 100%;
}
.table td {
padding: 8px;
text-align: left;
}
</style>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
delimiters: ['${', '}'],
data: function () {
return {
tempId: '{{ temp.id }}',
currency: '{{ temp.currency }}',
withdrawamtemps: `{{ withdrawamtemps }}`,
atmPremium: Number('{{ temp.withdrawpremium }}') / 100,
tip_options: null,
exchangeRate: null,
stack: [],
tipAmount: 0.0,
tipRounding: null,
hasNFC: false,
atmBox: false,
atmPin: null,
hidePin: true,
atmMode: false,
atmToken: '',
nfcTagReading: false,
lastPaymentsDialog: {
show: false,
data: []
},
invoiceDialog: {
show: false,
data: null,
dismissMsg: null,
paymentChecker: null
},
tipDialog: {
show: false
},
urlDialog: {
show: false
},
complete: {
show: false
},
rounding: false,
isFullScreen: false,
total: 0.0,
itemsTable: {
filter: '',
columns: [
{
name: 'delete',
align: 'left',
label: '',
field: ''
},
{
name: 'edit',
align: 'left',
label: '',
field: ''
},
{
name: 'id',
align: 'left',
label: 'ID',
field: 'id'
},
{
name: 'title',
align: 'left',
label: 'Title',
field: 'title'
},
{
name: 'price',
align: 'left',
label: 'Price',
field: 'price'
},
{
name: 'disabled',
align: 'left',
label: 'Disabled',
field: 'disabled'
}
],
pagination: {
rowsPerPage: 10
}
},
monochrome: this.$q.localStorage.getItem('lnbits.temp.color'),
showPoS: true,
cartDrawer: this.$q.screen.width > 1200,
searchTerm: '',
cart: new Map()
}
},
computed: {
amount: function () {
if (!this.stack.length) return 0.0
return this.stack.reduce((acc, dig) => acc * 10 + dig, 0) * 0.01
},
amountFormatted: function () {
return LNbits.utils.formatCurrency(
this.amount.toFixed(2),
this.currency
)
},
totalFormatted() {
return LNbits.utils.formatCurrency(this.total.toFixed(2), this.currency)
},
amountWithTipFormatted: function () {
return LNbits.utils.formatCurrency(
(this.amount + this.tipAmount).toFixed(2),
this.currency
)
},
sat: function () {
if (!this.exchangeRate) return 0
return Math.ceil(this.amount * this.exchangeRate)
},
totalSat: function () {
if (!this.exchangeRate) return 0
return Math.ceil(this.total * this.exchangeRate)
},
tipAmountSat: function () {
if (!this.exchangeRate) return 0
return Math.ceil(this.tipAmount * this.exchangeRate)
},
tipAmountFormatted: function () {
return LNbits.utils.formatSat(this.tipAmountSat)
},
fsat: function () {
return LNbits.utils.formatSat(this.sat)
},
totalfsat: function () {
return LNbits.utils.formatSat(this.totalSat)
},
isRoundValid() {
return this.tipRounding > this.amount
},
roundToSugestion() {
switch (true) {
case this.amount > 50:
toNext = 10
break
case this.amount > 6:
toNext = 5
break
case this.amount > 2.5:
toNext = 1
break
default:
toNext = 0.5
break
}
return Math.ceil(this.amount / toNext) * toNext
},
fullScreenIcon() {
return this.isFullScreen ? 'fullscreen_exit' : 'fullscreen'
},
filteredItems() {
if (!this.searchTerm) return this.items.filter(item => !item.disabled)
return this.items.filter(item => {
return (
!item.disabled &&
item.title.toLowerCase().includes(this.searchTerm.toLowerCase())
)
})
},
drawerWidth() {
return this.$q.screen.width < 500 ? 375 : 450
}
},
methods: {
addAmount() {
this.total = +(this.total + this.amount).toFixed(2)
this.stack = []
},
cancelAddAmount() {
this.total = 0.0
this.stack = []
},
addToCart(item, quantity = 1) {
if (this.cart.has(item.id)) {
this.cart.set(item.id, {
...this.cart.get(item.id),
quantity: this.cart.get(item.id).quantity + quantity
})
} else {
this.cart.set(item.id, {
...item,
quantity: quantity
})
}
this.total = this.total + item.price * quantity
},
removeFromCart(item, quantity = 1) {
let item_quantity = this.cart.get(item.id).quantity
if (item_quantity == 1 || item_quantity == quantity) {
this.cart.delete(item.id)
} else {
this.cart.set(item.id, {
...this.cart.get(item.id),
quantity: this.cart.get(item.id).quantity - quantity
})
}
this.total = this.total - item.price * quantity
},
clearCart() {
this.cart.clear()
this.total = 0.0
},
atm() {
if (this.withdrawamtemps > 0) {
this.atmBox = true
}
},
atmSubmit() {
self = this
LNbits.api
.request('GET', `/temp/api/v1/atm/` + this.tempId + `/` + this.atmPin)
.then(function (res) {
self.atmToken = res.data.id
if (res.data.claimed == false) {
self.atmBox = false
self.atmMode = true
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
atmGetWithdraw: function () {
self = this
var dialog = this.invoiceDialog
LNbits.api
.request(
'GET',
`/temp/api/v1/atm/withdraw/` + this.atmToken + `/` + this.sat
)
.then(function (res) {
lnurl = res.data.lnurl
dialog.data = {payment_request: lnurl}
dialog.show = true
dialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Withdraw...'
})
if (location.protocol !== 'http:') {
self.withdrawUrl =
'wss://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
self.atmToken
} else {
self.withdrawUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
self.atmToken
}
this.connectionWithdraw = new WebSocket(self.withdrawUrl)
this.connectionWithdraw.onmessage = function (e) {
if (e.data == 'paid') {
dialog.show = false
self.atmPin = null
self.atmToken = ''
self.complete.show = true
self.atmMode = false
}
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
setRounding() {
this.rounding = true
this.tipRounding = this.roundToSugestion
this.$nextTick(() => this.$refs.inputRounding.focus())
},
calculatePercent() {
let change = ((this.tipRounding - this.amount) / this.amount) * 100
if (change < 0) {
this.$q.notify({
type: 'warning',
message: 'Amount with tip must be greater than initial amount.'
})
this.tipRounding = this.roundToSugestion
return
}
this.processTipSelection(change)
},
closeInvoiceDialog: function () {
this.stack = []
this.tipAmount = 0.0
var dialog = this.invoiceDialog
setTimeout(function () {
clearInterval(dialog.paymentChecker)
dialog.dismissMsg()
}, 3000)
},
processTipSelection: function (selectedTipOption) {
this.tipDialog.show = false
if (!selectedTipOption) {
this.tipAmount = 0.0
return this.showInvoice()
}
this.tipAmount = (selectedTipOption / 100) * this.amount
this.showInvoice()
},
submitForm: function () {
if (this.total != 0.0) {
this.stack = Array.from(String(this.total * 100), Number)
}
if (this.tip_options && this.tip_options.length) {
this.rounding = false
this.tipRounding = null
this.showTipModal()
} else {
this.showInvoice()
}
},
showTipModal: function () {
if (!this.atmMode) {
this.tipDialog.show = true
} else {
this.showInvoice()
}
},
showInvoice: function () {
var self = this
if (self.atmMode) {
this.atmGetWithdraw()
} else {
var dialog = this.invoiceDialog
axios
.post('/temp/api/v1/temps/' + this.tempId + '/invoices', null, {
params: {
amount: this.sat,
memo: this.amountFormatted,
tipAmount: this.tipAmountSat
}
})
.then(function (response) {
dialog.data = response.data
dialog.show = true
dialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
dialog.paymentChecker = setInterval(function () {
axios
.get(
'/temp/api/v1/temps/' +
self.tempId +
'/invoices/' +
response.data.payment_hash
)
.then(function (res) {
if (res.data.paid) {
clearInterval(dialog.paymentChecker)
dialog.dismissMsg()
dialog.show = false
self.clearCart()
self.complete.show = true
}
})
}, 3000)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}
},
readNfcTag: function () {
try {
const self = this
if (typeof NDEFReader == 'undefined') {
throw {
toString: function () {
return 'NFC not supported on this device or browser.'
}
}
}
const ndef = new NDEFReader()
const readerAbortController = new AbortController()
readerAbortController.signal.onabort = event => {
console.debug('All NFC Read operations have been aborted.')
}
this.nfcTagReading = true
this.$q.notify({
message: 'Tap your NFC tag to pay this invoice with LNURLw.'
})
return ndef.scan({signal: readerAbortController.signal}).then(() => {
ndef.onreadingerror = () => {
self.nfcTagReading = false
this.$q.notify({
type: 'negative',
message: 'There was an error reading this NFC tag.'
})
readerAbortController.abort()
}
ndef.onreading = ({message}) => {
//Decode NDEF data from tag
const textDecoder = new TextDecoder('utf-8')
const remp= message.records.find(el => {
const payload = textDecoder.decode(el.data)
return payload.toUpperCase().indexOf('LNURL') !== -1
})
const lnurl = textDecoder.decode(record.data)
//User feedback, show loader icon
self.nfcTagReading = false
self.payInvoice(lnurl, readerAbortController)
this.$q.notify({
type: 'positive',
message: 'NFC tag read successfully.'
})
}
})
} catch (error) {
this.nfcTagReading = false
this.$q.notify({
type: 'negative',
message: error
? error.toString()
: 'An unexpected error has occurred.'
})
}
},
payInvoice: function (lnurl, readerAbortController) {
const self = this
return axios
.post(
'/temp/api/v1/temps/' +
self.tempId +
'/invoices/' +
self.invoiceDialog.data.payment_request +
'/pay',
{
lnurl: lnurl
}
)
.then(response => {
if (!response.data.success) {
this.$q.notify({
type: 'negative',
message: response.data.detail
})
}
readerAbortController.abort()
})
},
getRates() {
LNbits.api
.request('GET', `/temp/api/v1/rate/${this.currency}`)
.then(response => {
this.exchangeRate = response.data.rate
if (this.atmPremium > 0) {
this.exchangeRate = this.exchangeRate * (1 + this.atmPremium)
}
})
.catch(e => console.error(e))
},
getLastPayments() {
return axios
.get(`/temp/api/v1/temps/${this.tempId}/invoices`)
.then(res => {
if (res.data && res.data.length) {
let last = [...res.data]
this.lastPaymentsDialog.data = last.map(obj => {
obj.dateFrom = moment(obj.time * 1000).fromNow()
return obj
})
}
})
.catch(e => console.error(e))
},
showLastPayments() {
this.getLastPayments()
this.lastPaymentsDialog.show = true
},
toggleFullscreen() {
if (document.fullscreenElement) return document.exitFullscreen()
const elem = document.documentElement
elem
.requestFullscreen({navigationUI: 'show'})
.then(() => {
document.addEventListener('fullscreenchange', () => {
this.isFullScreen = document.fullscreenElement
})
})
.catch(err => console.error(err))
},
handleColorScheme(val) {
this.$q.localStorage.set('lnbits.temp.color', val)
}
},
created: function () {
var getRates = this.getRates
getRates()
this.tip_options =
'{{ temp.tip_options | tojson }}' == 'null'
? null
: JSON.parse('{{ temp.tip_options }}')
if ('{{ temp.tip_wallet }}') {
this.tip_options.push('Round')
}
this.items = JSON.parse(`{{ temp.items | safe }}`)
this.items.forEach((item, id) => {
item.formattedPrice = LNbits.utils.formatCurrency(
item.price,
this.currency
)
item.id = id
return item
})
if (this.items.length > 0) {
this.showPoS = false
}
setInterval(function () {
getRates()
}, 120000)
}
})
</script>
<style scoped>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
</style>
{% endblock %}