diff --git a/static/components/item-list.vue b/static/components/item-list.js
similarity index 72%
rename from static/components/item-list.vue
rename to static/components/item-list.js
index 3f408d7..d6e299f 100644
--- a/static/components/item-list.vue
+++ b/static/components/item-list.js
@@ -1,4 +1,23 @@
-
+window.app.component('item-list', {
+ name: 'item-list',
+ props: ['items', 'inclusive', 'format', 'currency', 'add-product'],
+ data: function () {
+ return {}
+ },
+ computed: {
+ },
+ methods: {
+ taxString(item) {
+ return `tax ${this.inclusive ? 'incl.' : 'excl.'} ${item.tax ? item.tax + '%' : ''}`
+ },
+ formatPrice(item) {
+ return `Price w/ tax: ${this.format(item.price * (1 + item.tax * 0.01), this.currency)}`
+ },
+ addToCart(item) {
+ this.$emit('add-product', item)
+ }
+ },
+ template: `
@@ -32,13 +51,13 @@
@@ -52,20 +71,5 @@
-
-
-
+ `
+})
diff --git a/static/js/index.js b/static/js/index.js
new file mode 100644
index 0000000..f140a86
--- /dev/null
+++ b/static/js/index.js
@@ -0,0 +1,487 @@
+const mapTpos = obj => {
+ obj.date = Quasar.date.formatDate(
+ new Date(obj.time * 1000),
+ 'YYYY-MM-DD HH:mm'
+ )
+ obj.tpos = ['/tpos/', obj.id].join('')
+ obj.shareUrl = [
+ window.location.protocol,
+ '//',
+ window.location.host,
+ obj.tpos
+ ].join('')
+ obj.items = obj.items ? JSON.parse(obj.items) : []
+ obj.itemsMap = new Map()
+ obj.items.forEach((item, idx) => {
+ let id = `${obj.id}:${idx + 1}`
+ obj.itemsMap.set(id, {...item, id})
+ })
+ return obj
+}
+
+window.app = Vue.createApp({
+ el: '#vue',
+ mixins: [window.windowMixin],
+ delimiters: ['${', '}'],
+ data: function () {
+ return {
+ tposs: [],
+ currencyOptions: [],
+ tpossTable: {
+ columns: [
+ {name: 'name', align: 'left', label: 'Name', field: 'name'},
+ {
+ name: 'currency',
+ align: 'left',
+ label: 'Currency',
+ field: 'currency'
+ },
+ {
+ name: 'withdraw_time_option',
+ align: 'left',
+ label: 'mins/sec',
+ field: 'withdraw_time_option'
+ },
+ {
+ name: 'withdraw_pin_disabled',
+ align: 'left',
+ label: 'atm pin disabled',
+ field: 'withdraw_pin_disabled'
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ }
+ },
+ withdraw_options: [
+ {
+ label: 'Mins',
+ value: 'mins'
+ },
+ {
+ label: 'Secs',
+ value: 'secs'
+ }
+ ],
+ formDialog: {
+ show: false,
+ data: {
+ withdraw_pin: null,
+ tip_options: [],
+ withdraw_between: 10,
+ withdraw_time_option: '',
+ withdraw_pin_disabled: false,
+ tax_inclusive: true
+ },
+ advanced: {
+ tips: false,
+ otc: false
+ }
+ },
+ itemDialog: {
+ show: false,
+ data: {
+ title: '',
+ image: '',
+ price: '',
+ categories: [],
+ disabled: false
+ }
+ },
+ tab: 'items',
+ itemsTable: {
+ columns: [
+ {
+ name: 'delete',
+ align: 'left',
+ label: '',
+ field: ''
+ },
+ {
+ name: 'edit',
+ align: 'left',
+ label: '',
+ field: ''
+ },
+ {
+ name: 'title',
+ align: 'left',
+ label: 'Title',
+ field: 'title'
+ },
+ {
+ name: 'price',
+ align: 'left',
+ label: 'Price',
+ field: 'price'
+ },
+ {
+ name: 'tax',
+ align: 'left',
+ label: 'Tax',
+ field: 'tax'
+ },
+ {
+ name: 'disabled',
+ align: 'left',
+ label: 'Disabled',
+ field: 'disabled'
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ }
+ },
+ fileDataDialog: {
+ show: false,
+ data: {},
+ import: () => {}
+ },
+ urlDialog: {
+ show: false,
+ data: {}
+ }
+ }
+ },
+ computed: {
+ categoryList() {
+ return Array.from(
+ new Set(
+ this.tposs.flatMap(tpos =>
+ tpos.items.flatMap(item => item.categories || [])
+ )
+ )
+ )
+ }
+ },
+ methods: {
+ generatePIN() {
+ // random 6 digit PIN
+ return Math.floor(100000 + Math.random() * 900000)
+ },
+ closeFormDialog() {
+ this.formDialog.show = false
+ this.formDialog.data = {
+ withdraw_pin: null,
+ tip_options: [],
+ withdraw_between: 10,
+ tax_inclusive: true
+ }
+ },
+ getTposs: function () {
+ var self = this
+
+ LNbits.api
+ .request(
+ 'GET',
+ '/tpos/api/v1/tposs?all_wallets=true',
+ this.g.user.wallets[0].inkey
+ )
+ .then(function (response) {
+ self.tposs = response.data.map(function (obj) {
+ return mapTpos(obj)
+ })
+ })
+ },
+ sendTposData() {
+ const data = {
+ ...this.formDialog.data,
+ tip_options:
+ this.formDialog.advanced.tips && this.formDialog.data.tip_options
+ ? JSON.stringify(
+ this.formDialog.data.tip_options.map(str => parseInt(str))
+ )
+ : JSON.stringify([]),
+ tip_wallet:
+ (this.formDialog.advanced.tips && this.formDialog.data.tip_wallet) ||
+ '',
+ items: JSON.stringify(this.formDialog.data.items)
+ }
+ // delete withdraw_between if value is empty string, defaults to 10 minutes
+ if (this.formDialog.data.withdraw_between == '') {
+ delete data.withdraw_between
+ }
+ if (!this.formDialog.advanced.otc) {
+ data.withdraw_limit = null
+ data.withdraw_pin = null
+ data.withdraw_premium = null
+ }
+ const wallet = _.findWhere(this.g.user.wallets, {
+ id: this.formDialog.data.wallet
+ })
+ if (data.id) {
+ this.updateTpos(wallet, data)
+ } else {
+ this.createTpos(wallet, data)
+ }
+ },
+ updateTposForm(tposId) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ this.formDialog.data = {
+ ...tpos,
+ tip_options: JSON.parse(tpos.tip_options)
+ }
+ if (this.formDialog.data.tip_wallet != '') {
+ this.formDialog.advanced.tips = true
+ }
+ if (this.formDialog.data.withdraw_limit >= 1) {
+ this.formDialog.advanced.otc = true
+ }
+ if (!this.formDialog.data.withdraw_pin) {
+ this.formDialog.data.withdraw_pin = this.generatePIN()
+ }
+ this.formDialog.show = true
+ },
+ createTpos(wallet, data) {
+ LNbits.api
+ .request('POST', '/tpos/api/v1/tposs', wallet.adminkey, data)
+ .then(response => {
+ this.tposs.push(mapTpos(response.data))
+ this.closeFormDialog()
+ })
+ .catch(error => {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ updateTpos(wallet, data) {
+ LNbits.api
+ .request('PUT', `/tpos/api/v1/tposs/${data.id}`, wallet.adminkey, data)
+ .then(response => {
+ this.tposs = _.reject(this.tposs, obj => {
+ return obj.id == data.id
+ })
+ this.tposs.push(mapTpos(response.data))
+ this.closeFormDialog()
+ })
+ .catch(error => {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ deleteTpos: function (tposId) {
+ var self = this
+ var tpos = _.findWhere(this.tposs, {id: tposId})
+
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this Tpos?')
+ .onOk(function () {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/tpos/api/v1/tposs/' + tposId,
+ _.findWhere(self.g.user.wallets, {id: tpos.wallet}).adminkey
+ )
+ .then(function (response) {
+ self.tposs = _.reject(self.tposs, function (obj) {
+ return obj.id == tposId
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ })
+ },
+ exportCSV: function () {
+ LNbits.utils.exportCSV(this.tpossTable.columns, this.tposs)
+ },
+ itemsArray(tposId) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ return [...tpos.itemsMap.values()]
+ },
+ itemFormatPrice(price, id) {
+ const tpos = id.split(':')[0]
+ const currency = _.findWhere(this.tposs, {id: tpos}).currency
+ if (currency == 'sats') {
+ return LNbits.utils.formatSat(price) + ' sat'
+ } else {
+ return LNbits.utils.formatCurrency(Number(price).toFixed(2), currency)
+ }
+ },
+ openItemDialog(id) {
+ const [tposId, itemId] = id.split(':')
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+
+ if (itemId) {
+ const item = tpos.itemsMap.get(id)
+ this.itemDialog.data = {
+ ...item,
+ tpos: tposId
+ }
+ } else {
+ this.itemDialog.data.tpos = tposId
+ }
+ this.itemDialog.taxInclusive = tpos.tax_inclusive
+ this.itemDialog.data.currency = tpos.currency
+ this.itemDialog.show = true
+ },
+ closeItemDialog() {
+ this.itemDialog.show = false
+ this.itemDialog.data = {
+ title: '',
+ image: '',
+ price: '',
+ categories: [],
+ disabled: false
+ }
+ },
+ deleteItem(id) {
+ const [tposId, itemId] = id.split(':')
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ const wallet = _.findWhere(this.g.user.wallets, {
+ id: tpos.wallet
+ })
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this item?')
+ .onOk(() => {
+ tpos.itemsMap.delete(id)
+ const data = {
+ items: [...tpos.itemsMap.values()]
+ }
+ this.updateTposItems(tpos.id, wallet, data)
+ })
+ },
+ addItems() {
+ const tpos = _.findWhere(this.tposs, {id: this.itemDialog.data.tpos})
+ const wallet = _.findWhere(this.g.user.wallets, {
+ id: tpos.wallet
+ })
+ if (this.itemDialog.data.id) {
+ tpos.itemsMap.set(this.itemDialog.data.id, this.itemDialog.data)
+ }
+ const data = {
+ items: this.itemDialog.data.id
+ ? [...tpos.itemsMap.values()]
+ : [...tpos.items, this.itemDialog.data]
+ }
+ this.updateTposItems(tpos.id, wallet, data)
+ },
+ deleteAllItems(tposId) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ const wallet = _.findWhere(this.g.user.wallets, {
+ id: tpos.wallet
+ })
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete ALL items?')
+ .onOk(() => {
+ tpos.itemsMap.clear()
+ const data = {
+ items: []
+ }
+ this.updateTposItems(tpos.id, wallet, data)
+ })
+ },
+ updateTposItems(tposId, wallet, data) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ if (tpos.tax_inclusive != this.taxInclusive) {
+ data.tax_inclusive = this.taxInclusive
+ }
+ LNbits.api
+ .request(
+ 'PUT',
+ `/tpos/api/v1/tposs/${tposId}/items`,
+ wallet.adminkey,
+ data
+ )
+ .then(response => {
+ this.tposs = _.reject(this.tposs, obj => {
+ return obj.id == tposId
+ })
+ this.tposs.push(mapTpos(response.data))
+ this.closeItemDialog()
+ })
+ .catch(error => {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ exportJSON(tposId) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ const data = [...tpos.items]
+ const filename = `items_${tpos.id}.json`
+ const json = JSON.stringify(data, null, 2)
+ let status = Quasar.exportFile(filename, json)
+ if (status !== true) {
+ this.$q.notify({
+ message: 'Browser denied file download...',
+ color: 'negative'
+ })
+ }
+ },
+ importJSON(tposId) {
+ try {
+ let input = document.getElementById('import')
+ input.click()
+ input.onchange = e => {
+ let file = e.target.files[0]
+ let reader = new FileReader()
+ reader.readAsText(file, 'UTF-8')
+ reader.onload = async readerEvent => {
+ try {
+ let content = readerEvent.target.result
+ let data = JSON.parse(content).filter(
+ obj => obj.title && obj.price
+ )
+ if (!data.length) {
+ throw new Error('Invalid JSON or missing data.')
+ }
+ this.openFileDataDialog(tposId, data)
+ } catch (error) {
+ this.$q.notify({
+ message: `Error importing file. ${error.message}`,
+ color: 'negative'
+ })
+ return
+ }
+ }
+ }
+ } catch (error) {
+ this.$q.notify({
+ message: 'Error importing file',
+ color: 'negative'
+ })
+ }
+ },
+ openFileDataDialog(tposId, data) {
+ const tpos = _.findWhere(this.tposs, {id: tposId})
+ const wallet = _.findWhere(this.g.user.wallets, {
+ id: tpos.wallet
+ })
+ data.forEach(item => {
+ item.formattedPrice = this.formatAmount(item.price, tpos.currency)
+ })
+ this.fileDataDialog.data = data
+ this.fileDataDialog.count = data.length
+ this.fileDataDialog.show = true
+ this.fileDataDialog.import = () => {
+ let updatedData = {
+ items: [...tpos.items, ...data]
+ }
+ this.updateTposItems(tpos.id, wallet, updatedData)
+ this.fileDataDialog.data = {}
+ this.fileDataDialog.show = false
+ }
+ },
+ openUrlDialog(id) {
+ this.urlDialog.data = _.findWhere(this.tposs, {id})
+ this.urlDialog.show = true
+ },
+ formatAmount: function (amount, currency) {
+ if (currency == 'sats') {
+ return LNbits.utils.formatSat(amount) + ' sat'
+ } else {
+ return LNbits.utils.formatCurrency(Number(amount).toFixed(2), currency)
+ }
+ }
+ },
+ created: function () {
+ if (this.g.user.wallets.length) {
+ this.getTposs()
+ }
+ LNbits.api
+ .request('GET', '/api/v1/currencies')
+ .then(response => {
+ this.currencyOptions = ['satoshis', ...response.data]
+ })
+ .catch(err => {
+ LNbits.utils.notifyApiError(err)
+ })
+ }
+})
diff --git a/static/js/tpos.js b/static/js/tpos.js
index 9e23cde..e7d43ff 100644
--- a/static/js/tpos.js
+++ b/static/js/tpos.js
@@ -1,776 +1,774 @@
-window.app.createVue({
- el: '#vue',
- mixins: [window.windowMixin],
- delimiters: ['${', '}'],
- data: function () {
- return {
- tposId: tpos.id,
- currency: tpos.currency,
- atmPremium: tpos.withdraw_premium / 100,
- withdraw_maximum: withdraw_maximum,
- withdraw_pin_open: withdraw_pin_open,
- 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,
- cartTax: 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.tpos.color') || false,
- showPoS: true,
- cartDrawer: this.$q.screen.width > 1200,
- searchTerm: '',
- categoryFilter: '',
- cart: new Map(),
- denomIsSats: tpos.currency == 'sats'
+window.app = Vue.createApp({
+ el: '#vue',
+ mixins: [window.windowMixin],
+ delimiters: ['${', '}'],
+ data: function () {
+ return {
+ tposId: tpos.id,
+ currency: tpos.currency,
+ atmPremium: tpos.withdraw_premium / 100,
+ withdraw_maximum: withdraw_maximum,
+ withdraw_pin_open: withdraw_pin_open,
+ 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,
+ cartTax: 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.tpos.color') || false,
+ showPoS: true,
+ cartDrawer: this.$q.screen.width > 1200,
+ searchTerm: '',
+ categoryFilter: '',
+ cart: new Map(),
+ denomIsSats: tpos.currency == 'sats'
+ }
+ },
+ computed: {
+ amount: function () {
+ if (!this.stack.length) return 0.0
+ return (
+ this.stack.reduce((acc, dig) => acc * 10 + dig, 0) *
+ (this.currency == 'sats' ? 1 : 0.01)
+ )
},
- computed: {
- amount: function () {
- if (!this.stack.length) return 0.0
- return (
- this.stack.reduce((acc, dig) => acc * 10 + dig, 0) *
- (this.currency == 'sats' ? 1 : 0.01)
- )
- },
- amountFormatted: function () {
- return this.formatAmount(this.amount, this.currency)
- },
- totalFormatted() {
- return this.formatAmount(this.total, this.currency)
- },
- amountWithTipFormatted: function () {
- return this.formatAmount(this.amount + this.tipAmount, 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)
- },
- 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
- }
+ amountFormatted: function () {
+ return this.formatAmount(this.amount, this.currency)
+ },
+ totalFormatted() {
+ return this.formatAmount(this.total, this.currency)
+ },
+ amountWithTipFormatted: function () {
+ return this.formatAmount(this.amount + this.tipAmount, 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)
+ },
+ 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() {
+ // filter out disabled items
+ let items = this.items.filter(item => !item.disabled)
+ // if searchTerm entered, filter out items that don't match
+ if (this.searchTerm) {
+ items = items.filter(item => {
+ return item.title
+ .toLowerCase()
+ .includes(this.searchTerm.toLowerCase())
+ })
+ }
+ // if categoryFilter entered, filter out items that don't match
+ if (this.categoryFilter) {
+ items = items.filter(item => {
+ return item.categories
+ .map(c => c.toLowerCase())
+ .includes(this.categoryFilter.toLowerCase())
+ })
+ }
+ return items
+ },
+ drawerWidth() {
+ return this.$q.screen.width < 500 ? 375 : 450
+ },
+ formattedCartTax() {
+ return this.formatAmount(this.cartTax, this.currency)
+ },
+ taxSubtotal() {
+ if (this.taxInclusive) return this.totalFormatted
+ return this.formatAmount(
+ Math.floor(this.total - this.cartTax),
+ this.currency
+ )
+ },
+ keypadDisabled() {
+ return !this.exchangeRate
+ }
+ },
+ 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 + this.calculateItemPrice(item, quantity)
+ this.cartTaxTotal()
+ },
+ 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 - this.calculateItemPrice(item, quantity)
+ this.cartTaxTotal()
+ },
+ calculateItemPrice(item, qty) {
+ let tax = item.tax || this.taxDefault
+ if (!tax || this.taxInclusive) return item.price * qty
- return Math.ceil(this.amount / toNext) * toNext
- },
- fullScreenIcon() {
- return this.isFullScreen ? 'fullscreen_exit' : 'fullscreen'
- },
- filteredItems() {
- // filter out disabled items
- let items = this.items.filter(item => !item.disabled)
- // if searchTerm entered, filter out items that don't match
- if (this.searchTerm) {
- items = items.filter(item => {
- return item.title
- .toLowerCase()
- .includes(this.searchTerm.toLowerCase())
- })
- }
- // if categoryFilter entered, filter out items that don't match
- if (this.categoryFilter) {
- items = items.filter(item => {
- return item.categories
- .map(c => c.toLowerCase())
- .includes(this.categoryFilter.toLowerCase())
- })
+ // add tax to price
+ return item.price * (1 + tax * 0.01) * qty
+ },
+ cartTaxTotal() {
+ if (!this.cart.size) return 0.0
+ let total = 0.0
+ for (let item of this.cart.values()) {
+ let tax = item.tax || this.taxDefault
+ total += item.price * item.quantity * (tax * 0.01)
+ }
+ this.cartTax = total
+ },
+ clearCart() {
+ this.cart.clear()
+ this.cartTax = 0.0
+ this.total = 0.0
+ },
+ atm() {
+ if (this.atmPremium > 0) {
+ this.exchangeRate = this.exchangeRate / (1 + this.atmPremium)
+ }
+ if (this.withdraw_pin_open != 0) {
+ this.atmPin = this.withdraw_pin_open
+ this.atmSubmit()
+ return
+ }
+ if (this.withdraw_maximum > 0) {
+ this.atmBox = true
+ }
+ },
+ atmSubmit() {
+ self = this
+ LNbits.api
+ .request('GET', `/tpos/api/v1/atm/` + this.tposId + `/` + 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
+ if (this.sat > this.withdraw_maximum) {
+ this.$q.notify({
+ type: 'negative',
+ message: 'Amount exceeds the maximum withdrawal limit.'
+ })
+ return
+ }
+ LNbits.api
+ .request(
+ 'GET',
+ `/tpos/api/v1/atm/withdraw/` + this.atmToken + `/` + this.sat
+ )
+ .then(function (res) {
+ if (res.data.status == 'ERROR') {
+ self.$q.notify({
+ type: 'negative',
+ message: res.data.reason
+ })
+ return
+ }
+ lnurl = res.data.lnurl
+ dialog.data = {payment_request: lnurl}
+ dialog.show = true
+ self.readNfcTag()
+ 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 = e => {
+ if (e.data == 'paid') {
+ dialog.show = false
+ self.atmPin = null
+ self.atmToken = ''
+ self.complete.show = true
+ self.atmMode = false
+ this.connectionWithdraw.close()
}
- return items
- },
- drawerWidth() {
- return this.$q.screen.width < 500 ? 375 : 450
- },
- formattedCartTax() {
- return this.formatAmount(this.cartTax, this.currency)
- },
- taxSubtotal() {
- if (this.taxInclusive) return this.totalFormatted
- return this.formatAmount(
- Math.floor(this.total - this.cartTax),
- this.currency
- )
- },
- keypadDisabled() {
- return !this.exchangeRate
+ }
+ self.getRates()
+ })
+ .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) {
+ if (this.amount > 0.0) {
+ this.total = this.total + this.amount
+ }
+ if (this.currency == 'sats') {
+ this.stack = Array.from(String(Math.ceil(this.total), Number))
+ } else {
+ this.stack = Array.from(String(Math.ceil(this.total * 100)), Number)
}
+ }
+
+ if (!this.exchangeRate || this.exchangeRate == 0 || this.sat == 0) {
+ this.$q.notify({
+ type: 'negative',
+ message:
+ 'Exchange rate not available, or wrong value. Please try again later.'
+ })
+ return
+ }
+
+ if (this.tip_options && this.tip_options.length) {
+ this.rounding = false
+ this.tipRounding = null
+ this.showTipModal()
+ } else {
+ this.showInvoice()
+ }
},
- 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 + this.calculateItemPrice(item, quantity)
- this.cartTaxTotal()
- },
- 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 - this.calculateItemPrice(item, quantity)
- this.cartTaxTotal()
- },
- calculateItemPrice(item, qty) {
- let tax = item.tax || this.taxDefault
- if (!tax || this.taxInclusive) return item.price * qty
-
- // add tax to price
- return item.price * (1 + tax * 0.01) * qty
- },
- cartTaxTotal() {
- if (!this.cart.size) return 0.0
- let total = 0.0
- for (let item of this.cart.values()) {
- let tax = item.tax || this.taxDefault
- total += item.price * item.quantity * (tax * 0.01)
- }
- this.cartTax = total
- },
- clearCart() {
- this.cart.clear()
- this.cartTax = 0.0
- this.total = 0.0
- },
- atm() {
- if (this.atmPremium > 0) {
- this.exchangeRate = this.exchangeRate / (1 + this.atmPremium)
- }
- if (this.withdraw_pin_open != 0) {
- this.atmPin = this.withdraw_pin_open
- this.atmSubmit()
- return
- }
- if (this.withdraw_maximum > 0) {
- this.atmBox = true
- }
- },
- atmSubmit() {
- self = this
- LNbits.api
- .request('GET', `/tpos/api/v1/atm/` + this.tposId + `/` + 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
- if (this.sat > this.withdraw_maximum) {
- this.$q.notify({
- type: 'negative',
- message: 'Amount exceeds the maximum withdrawal limit.'
- })
- return
+ 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
+
+ let params = {
+ amount: this.sat,
+ memo: this.amountFormatted,
+ tipAmount: this.tipAmountSat
+ }
+ if (this.cart.size) {
+ let details = [...this.cart.values()].map(item => {
+ return {
+ price: item.price,
+ formattedPrice: item.formattedPrice,
+ quantity: item.quantity,
+ title: item.title,
+ tax: item.tax || this.taxDefault
}
- LNbits.api
- .request(
- 'GET',
- `/tpos/api/v1/atm/withdraw/` + this.atmToken + `/` + this.sat
+ })
+
+ params.details = {
+ currency: this.currency,
+ exchangeRate: this.exchangeRate,
+ items: details,
+ taxIncluded: this.taxInclusive,
+ taxValue: this.cartTax
+ }
+ }
+
+ axios
+ .post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, {
+ params: {...params}
+ })
+ .then(function (response) {
+ dialog.data = response.data
+ dialog.show = true
+ self.readNfcTag()
+
+ dialog.dismissMsg = self.$q.notify({
+ timeout: 0,
+ message: 'Waiting for payment...'
+ })
+
+ dialog.paymentChecker = setInterval(function () {
+ axios
+ .get(
+ '/tpos/api/v1/tposs/' +
+ self.tposId +
+ '/invoices/' +
+ response.data.payment_hash
)
.then(function (res) {
- if (res.data.status == 'ERROR') {
- self.$q.notify({
- type: 'negative',
- message: res.data.reason
- })
- return
- }
- lnurl = res.data.lnurl
- dialog.data = {payment_request: lnurl}
- dialog.show = true
- self.readNfcTag()
- 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 = e => {
- if (e.data == 'paid') {
- dialog.show = false
- self.atmPin = null
- self.atmToken = ''
- self.complete.show = true
- self.atmMode = false
- this.connectionWithdraw.close()
- }
- }
- self.getRates()
- })
- .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.'
+ if (res.data.paid) {
+ clearInterval(dialog.paymentChecker)
+ dialog.dismissMsg()
+ dialog.show = false
+ self.clearCart()
+
+ self.complete.show = true
+ }
})
- 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
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ }
+ },
+ readNfcTag: function () {
+ try {
+ const self = this
- if (!selectedTipOption) {
- this.tipAmount = 0.0
- return this.showInvoice()
- }
+ if (typeof NDEFReader == 'undefined') {
+ console.debug('NFC not supported on this device or browser.')
+ return
+ }
- this.tipAmount = (selectedTipOption / 100) * this.amount
- this.showInvoice()
- },
- submitForm: function () {
- if (this.total != 0.0) {
- if (this.amount > 0.0) {
- this.total = this.total + this.amount
- }
- if (this.currency == 'sats') {
- this.stack = Array.from(String(Math.ceil(this.total), Number))
- } else {
- this.stack = Array.from(String(Math.ceil(this.total * 100)), Number)
- }
- }
+ const ndef = new NDEFReader()
- if (!this.exchangeRate || this.exchangeRate == 0 || this.sat == 0) {
- this.$q.notify({
- type: 'negative',
- message:
- 'Exchange rate not available, or wrong value. Please try again later.'
- })
- return
- }
+ const readerAbortController = new AbortController()
+ readerAbortController.signal.onabort = event => {
+ console.debug('All NFC Read operations have been aborted.')
+ }
- 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
-
- let params = {
- amount: this.sat,
- memo: this.amountFormatted,
- tipAmount: this.tipAmountSat
- }
- if (this.cart.size) {
- let details = [...this.cart.values()].map(item => {
- return {
- price: item.price,
- formattedPrice: item.formattedPrice,
- quantity: item.quantity,
- title: item.title,
- tax: item.tax || this.taxDefault
- }
- })
-
- params.details = {
- currency: this.currency,
- exchangeRate: this.exchangeRate,
- items: details,
- taxIncluded: this.taxInclusive,
- taxValue: this.cartTax
- }
- }
-
- axios
- .post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, {
- params: {...params}
- })
- .then(function (response) {
- dialog.data = response.data
- dialog.show = true
- self.readNfcTag()
-
- dialog.dismissMsg = self.$q.notify({
- timeout: 0,
- message: 'Waiting for payment...'
- })
-
- dialog.paymentChecker = setInterval(function () {
- axios
- .get(
- '/tpos/api/v1/tposs/' +
- self.tposId +
- '/invoices/' +
- response.data.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') {
- console.debug('NFC not supported on this device or browser.')
- return
- }
-
- 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: this.atmMode
- ? 'Tap your NFC tag to withdraw with LNURLp.'
- : 'Tap your NFC tag to pay this invoice with LNURLw.'
- })
+ this.nfcTagReading = true
+ this.$q.notify({
+ message: this.atmMode
+ ? 'Tap your NFC tag to withdraw with LNURLp.'
+ : '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 record = 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
- if (self.atmMode) {
- const URL = lnurl.replace('lnurlp://', 'https://')
- LNbits.api
- .request('GET', `${lnurl.replace('lnurlw://', 'https://')}`)
- .then(res => {
- self.makeWithdraw(res.data.payLink, readerAbortController)
- })
- } else {
- 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.'
- })
- }
- },
- makeWithdraw(payLink, readerAbortController) {
- if (!payLink) {
- this.$q.notify({
- type: 'negative',
- message: 'LNURL not found in NFC tag.'
- })
- return
- }
- LNbits.api
- .request(
- 'GET',
- `/tpos/api/v1/atm/withdraw/${this.atmToken}/${this.sat}/pay?payLink=${payLink}`
- )
- .then(res => {
- if (!res.data.success) {
- this.$q.notify({
- type: 'negative',
- message: res.data.detail
- })
- } else {
- this.stack = []
- this.total = 0.0
- this.$q.notify({
- type: 'positive',
- message: 'Topup successful!'
- })
- }
- readerAbortController.abort()
- })
- .catch(e => {
- console.error(e)
- readerAbortController.abort()
- })
- },
- payInvoice: function (lnurl, readerAbortController) {
- const self = this
+ return ndef.scan({signal: readerAbortController.signal}).then(() => {
+ ndef.onreadingerror = () => {
+ self.nfcTagReading = false
- return axios
- .post(
- '/tpos/api/v1/tposs/' +
- self.tposId +
- '/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() {
- if (this.currency == 'sats') {
- this.exchangeRate = 1
- } else {
- LNbits.api
- .request('GET', `/tpos/api/v1/rate/${this.currency}`)
- .then(response => {
- this.exchangeRate = response.data.rate
- })
- .catch(e => console.error(e))
- }
- },
- getLastPayments() {
- return axios
- .get(`/tpos/api/v1/tposs/${this.tposId}/invoices`)
+ 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 record = 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
+ if (self.atmMode) {
+ const URL = lnurl.replace('lnurlp://', 'https://')
+ LNbits.api
+ .request('GET', `${lnurl.replace('lnurlw://', 'https://')}`)
.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
- })
- }
+ self.makeWithdraw(res.data.payLink, readerAbortController)
})
- .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.tpos.color', val)
- },
- extractCategories(items) {
- let categories = new Set()
- items
- .map(item => {
- item.categories &&
- item.categories.forEach(category => categories.add(category))
- })
- .filter(Boolean)
-
- if (categories.size) {
- categories = ['All', ...categories]
- }
- return Array.from(categories)
- },
- handleCategoryBtn(category) {
- if (this.categoryFilter == category) {
- this.categoryFilter = ''
- } else {
- this.categoryFilter = category == 'All' ? '' : category
- }
- },
- formatAmount: function (amount, currency) {
- if (currency == 'sats') {
- return LNbits.utils.formatSat(amount) + ' sat'
} else {
- return LNbits.utils.formatCurrency(
- Number(amount).toFixed(2),
- currency
- )
+ 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.'
+ })
+ }
+ },
+ makeWithdraw(payLink, readerAbortController) {
+ if (!payLink) {
+ this.$q.notify({
+ type: 'negative',
+ message: 'LNURL not found in NFC tag.'
+ })
+ return
+ }
+ LNbits.api
+ .request(
+ 'GET',
+ `/tpos/api/v1/atm/withdraw/${this.atmToken}/${this.sat}/pay?payLink=${payLink}`
+ )
+ .then(res => {
+ if (!res.data.success) {
+ this.$q.notify({
+ type: 'negative',
+ message: res.data.detail
+ })
+ } else {
+ this.stack = []
+ this.total = 0.0
+ this.$q.notify({
+ type: 'positive',
+ message: 'Topup successful!'
+ })
+ }
+ readerAbortController.abort()
+ })
+ .catch(e => {
+ console.error(e)
+ readerAbortController.abort()
+ })
+ },
+ payInvoice: function (lnurl, readerAbortController) {
+ const self = this
+
+ return axios
+ .post(
+ '/tpos/api/v1/tposs/' +
+ self.tposId +
+ '/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()
+ })
},
- created: function () {
- var getRates = this.getRates
- getRates()
- this.pinDisabled = tpos.withdraw_pin_disabled
- this.taxInclusive = tpos.tax_inclusive
- this.taxDefault = tpos.tax_default
+ getRates() {
+ if (this.currency == 'sats') {
+ this.exchangeRate = 1
+ } else {
+ LNbits.api
+ .request('GET', `/tpos/api/v1/rate/${this.currency}`)
+ .then(response => {
+ this.exchangeRate = response.data.rate
+ })
+ .catch(e => console.error(e))
+ }
+ },
+ getLastPayments() {
+ return axios
+ .get(`/tpos/api/v1/tposs/${this.tposId}/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.tpos.color', val)
+ },
+ extractCategories(items) {
+ let categories = new Set()
+ items
+ .map(item => {
+ item.categories &&
+ item.categories.forEach(category => categories.add(category))
+ })
+ .filter(Boolean)
- this.tip_options = tpos.tip_options == 'null' ? null : tpos.tip_options
+ if (categories.size) {
+ categories = ['All', ...categories]
+ }
+ return Array.from(categories)
+ },
+ handleCategoryBtn(category) {
+ if (this.categoryFilter == category) {
+ this.categoryFilter = ''
+ } else {
+ this.categoryFilter = category == 'All' ? '' : category
+ }
+ },
+ formatAmount: function (amount, currency) {
+ if (currency == 'sats') {
+ return LNbits.utils.formatSat(amount) + ' sat'
+ } else {
+ return LNbits.utils.formatCurrency(Number(amount).toFixed(2), currency)
+ }
+ }
+ },
+ created: function () {
+ var getRates = this.getRates
+ getRates()
+ console.log(tpos)
+ this.pinDisabled = tpos.withdraw_pin_disabled
+ this.taxInclusive = tpos.tax_inclusive
+ this.taxDefault = tpos.tax_default
+
+ this.tip_options = tpos.tip_options == 'null' ? null : tpos.tip_options
+
+ if (tpos.tip_wallet) {
+ this.tip_options.push('Round')
+ }
- if (tpos.tip_wallet) {
- this.tip_options.push('Round')
- }
+ this.items = tpos.items
+ this.items.forEach((item, id) => {
+ item.formattedPrice = this.formatAmount(item.price, this.currency)
+ item.id = id
+ return item
+ })
+ if (this.items.length > 0) {
+ this.showPoS = false
+ this.categories = this.extractCategories(this.items)
+ }
- this.items = tpos.items
- this.items.forEach((item, id) => {
- item.formattedPrice = this.formatAmount(item.price, this.currency)
- item.id = id
- return item
- })
- if (this.items.length > 0) {
- this.showPoS = false
- this.categories = this.extractCategories(this.items)
+ window.addEventListener('keyup', event => {
+ // do nothing if the event was already processed
+ if (event.defaultPrevented) return
+
+ // active only in the the PoS mode, not in the Cart mode or ATM pin
+ if (!this.showPoS || this.atmBox) return
+
+ // prevent weird behaviour when setting round tip
+ if (this.tipDialog.show) return
+
+ const {key} = event
+ if (key >= '0' && key <= '9') {
+ // buttons 0 ... 9
+ this.stack.push(Number(key))
+ } else
+ switch (key) {
+ case 'Backspace': // button ⬅
+ this.stack.pop()
+ break
+ case 'Enter': // button OK
+ this.submitForm()
+ break
+ case 'c': // button C
+ case 'C':
+ case 'Escape':
+ if (this.total > 0.0) {
+ this.cancelAddAmount()
+ } else {
+ this.stack = []
+ }
+ break
+ case '+': // button +
+ this.addAmount()
+ break
+ default:
+ // return if we didn't handle anything
+ return
}
- window.addEventListener('keyup', event => {
- // do nothing if the event was already processed
- if (event.defaultPrevented) return
-
- // active only in the the PoS mode, not in the Cart mode or ATM pin
- if (!this.showPoS || this.atmBox) return
-
- // prevent weird behaviour when setting round tip
- if (this.tipDialog.show) return
-
- const {key} = event
- if (key >= '0' && key <= '9') {
- // buttons 0 ... 9
- this.stack.push(Number(key))
- } else
- switch (key) {
- case 'Backspace': // button ⬅
- this.stack.pop()
- break
- case 'Enter': // button OK
- this.submitForm()
- break
- case 'c': // button C
- case 'C':
- case 'Escape':
- if (this.total > 0.0) {
- this.cancelAddAmount()
- } else {
- this.stack = []
- }
- break
- case '+': // button +
- this.addAmount()
- break
- default:
- // return if we didn't handle anything
- return
- }
-
- // cancel the default action to avoid it being handled twice
- event.preventDefault()
- })
+ // cancel the default action to avoid it being handled twice
+ event.preventDefault()
+ })
- setInterval(function () {
- getRates()
- }, 120000)
- }
+ setInterval(function () {
+ getRates()
+ }, 120000)
+ }
})
diff --git a/static/js/utils.js b/static/js/utils.js
deleted file mode 100644
index 11ebc81..0000000
--- a/static/js/utils.js
+++ /dev/null
@@ -1,18 +0,0 @@
-function loadTemplateAsync(path) {
- const result = new Promise(resolve => {
- const xhttp = new XMLHttpRequest()
-
- xhttp.onreadystatechange = function () {
- if (this.readyState == 4) {
- if (this.status == 200) resolve(this.responseText)
-
- if (this.status == 404) resolve(`
Page not found: ${path}
`)
- }
- }
-
- xhttp.open('GET', path, true)
- xhttp.send()
- })
-
- return result
-}
diff --git a/templates/tpos/index.html b/templates/tpos/index.html
index 345e597..44d6701 100644
--- a/templates/tpos/index.html
+++ b/templates/tpos/index.html
@@ -26,7 +26,7 @@ TPoS
TPoS
-
- ${ col.label }
-
+
Withdraw PIN
Withdraw Limit
Withdraw Premium
@@ -82,15 +85,6 @@ TPoS
>
- Click to copy${props.row.id.substring(0,6)}...
TPoS
- ${ (col.name == 'tip_options' && col.value ?
- JSON.parse(col.value).join(", ") : col.value) }
- N/A
${props.row.withdraw_pin}
@@ -212,7 +203,7 @@ TPoS
TPoS
v-for="col in props.cols"
:key="col.name"
:props="props"
- >
- ${ col.label }
-
+ v-text="col.label"
+ >
@@ -627,679 +617,5 @@
{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
+
{% endblock %}
diff --git a/templates/tpos/tpos.html b/templates/tpos/tpos.html
index 49c797e..5af4a82 100644
--- a/templates/tpos/tpos.html
+++ b/templates/tpos/tpos.html
@@ -527,9 +527,6 @@
}
{% endblock %} {% block scripts %}
-
-
-
-
+