1205 lines
34 KiB
HTML
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 %}
|