commit
d106879a09
17 changed files with 580 additions and 96 deletions
|
|
@ -185,7 +185,7 @@ async function customerStall(path) {
|
|||
items: Array.from(this.cart.products, p => {
|
||||
return {product_id: p[0], quantity: p[1].quantity}
|
||||
}),
|
||||
shipping: orderData.shippingzone
|
||||
shipping_id: orderData.shippingzone
|
||||
}
|
||||
orderObj.id = await hash(
|
||||
[orderData.pubkey, created_at, JSON.stringify(orderObj)].join(':')
|
||||
|
|
@ -269,7 +269,7 @@ async function customerStall(path) {
|
|||
items: Array.from(this.cart.products, p => {
|
||||
return {product_id: p[0], quantity: p[1].quantity}
|
||||
}),
|
||||
shipping: orderData.shippingzone
|
||||
shipping_id: orderData.shippingzone
|
||||
}
|
||||
let created_at = Math.floor(Date.now() / 1000)
|
||||
orderObj.id = await hash(
|
||||
|
|
@ -375,8 +375,9 @@ async function customerStall(path) {
|
|||
this.qrCodeDialog.data.message = json.message
|
||||
return cb()
|
||||
}
|
||||
let payment_request = json.payment_options.find(o => o.type == 'ln')
|
||||
.link
|
||||
let payment_request = json.payment_options.find(
|
||||
o => o.type == 'ln'
|
||||
).link
|
||||
if (!payment_request) return
|
||||
this.loading = false
|
||||
this.qrCodeDialog.data.payment_request = payment_request
|
||||
|
|
|
|||
|
|
@ -1,7 +1,26 @@
|
|||
<div>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-badge v-if="unreadMessages" color="green"
|
||||
><span v-text="unreadMessages"></span> new</q-badge
|
||||
>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-btn
|
||||
v-if="activePublicKey"
|
||||
@click="showClientOrders"
|
||||
unelevated
|
||||
outline
|
||||
class="float-right"
|
||||
>Client Orders</q-btn
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
|
|
@ -9,7 +28,7 @@
|
|||
<q-card-section>
|
||||
<q-select
|
||||
v-model="activePublicKey"
|
||||
:options="customersPublicKeys.map(k => ({label: `${k.slice(0, 16)}...${k.slice(k.length - 16)}`, value: k}))"
|
||||
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
|
||||
label="Select Customer"
|
||||
emit-value
|
||||
@input="selectActiveCustomer()"
|
||||
|
|
|
|||
|
|
@ -2,23 +2,38 @@ async function directMessages(path) {
|
|||
const template = await loadTemplateAsync(path)
|
||||
Vue.component('direct-messages', {
|
||||
name: 'direct-messages',
|
||||
props: ['active-public-key', 'adminkey', 'inkey'],
|
||||
props: ['active-chat-customer', 'adminkey', 'inkey'],
|
||||
template,
|
||||
|
||||
watch: {
|
||||
activeChatCustomer: async function (n) {
|
||||
this.activePublicKey = n
|
||||
},
|
||||
activePublicKey: async function (n) {
|
||||
await this.getDirectMessages(n)
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
customersPublicKeys: [],
|
||||
customers: [],
|
||||
unreadMessages: 0,
|
||||
activePublicKey: null,
|
||||
messages: [],
|
||||
newMessage: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendMessage: async function () {},
|
||||
buildCustomerLabel: function (c) {
|
||||
let label = `${c.profile.name || 'unknown'} ${c.profile.about || ''}`
|
||||
if (c.unread_messages) {
|
||||
label += `[new: ${c.unread_messages}]`
|
||||
}
|
||||
label += ` (${c.public_key.slice(0, 16)}...${c.public_key.slice(
|
||||
c.public_key.length - 16
|
||||
)}`
|
||||
return label
|
||||
},
|
||||
getDirectMessages: async function (pubkey) {
|
||||
if (!pubkey) {
|
||||
this.messages = []
|
||||
|
|
@ -31,23 +46,21 @@ async function directMessages(path) {
|
|||
this.inkey
|
||||
)
|
||||
this.messages = data
|
||||
console.log(
|
||||
'### this.messages',
|
||||
this.messages.map(m => m.message)
|
||||
)
|
||||
|
||||
this.focusOnChatBox(this.messages.length - 1)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
getCustomersPublicKeys: async function () {
|
||||
getCustomers: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1/customers',
|
||||
this.inkey
|
||||
)
|
||||
this.customersPublicKeys = data
|
||||
this.customers = data
|
||||
this.unreadMessages = data.filter(c => c.unread_messages).length
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
|
|
@ -70,8 +83,19 @@ async function directMessages(path) {
|
|||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
handleNewMessage: async function (data) {
|
||||
if (data.customerPubkey === this.activePublicKey) {
|
||||
await this.getDirectMessages(this.activePublicKey)
|
||||
} else {
|
||||
await this.getCustomers()
|
||||
}
|
||||
},
|
||||
showClientOrders: function () {
|
||||
this.$emit('customer-selected', this.activePublicKey)
|
||||
},
|
||||
selectActiveCustomer: async function () {
|
||||
await this.getDirectMessages(this.activePublicKey)
|
||||
await this.getCustomers()
|
||||
},
|
||||
focusOnChatBox: function (index) {
|
||||
setTimeout(() => {
|
||||
|
|
@ -85,7 +109,7 @@ async function directMessages(path) {
|
|||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getCustomersPublicKeys()
|
||||
await this.getCustomers()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,45 @@
|
|||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<div class="col">
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.publicKey"
|
||||
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
|
||||
label="Customer"
|
||||
emit-value
|
||||
class="text-wrap"
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.isPaid"
|
||||
:options="ternaryOptions"
|
||||
label="Paid"
|
||||
emit-value
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.isShipped"
|
||||
:options="ternaryOptions"
|
||||
label="Shipped"
|
||||
emit-value
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-btn
|
||||
unelevated
|
||||
color="secondary"
|
||||
outline
|
||||
icon="refresh"
|
||||
icon="search"
|
||||
@click="getOrders()"
|
||||
class="float-left"
|
||||
>Refresh Orders</q-btn
|
||||
class="float-right"
|
||||
>Search Orders</q-btn
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row q-mt-md">
|
||||
<div class="col">
|
||||
<q-table
|
||||
flat
|
||||
|
|
@ -36,9 +63,18 @@
|
|||
/>
|
||||
</q-td>
|
||||
|
||||
<q-td key="id" :props="props"> {{toShortId(props.row.id)}} </q-td>
|
||||
<q-td key="total" :props="props"> {{props.row.total}} </q-td>
|
||||
<!-- todo: currency per order -->
|
||||
<q-td key="id" :props="props">
|
||||
{{toShortId(props.row.id)}}
|
||||
<q-badge v-if="props.row.isNew" color="orange">new</q-badge></q-td
|
||||
>
|
||||
<q-td key="total" :props="props">
|
||||
{{satBtc(props.row.total)}}
|
||||
</q-td>
|
||||
<q-td key="fiat" :props="props">
|
||||
<span v-if="props.row.extra.currency !== 'sat'">
|
||||
{{orderTotal(props.row)}} {{props.row.extra.currency}}
|
||||
</span>
|
||||
</q-td>
|
||||
|
||||
<q-td key="paid" :props="props">
|
||||
<q-checkbox
|
||||
|
|
@ -78,7 +114,9 @@
|
|||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-1">Quantity</div>
|
||||
<div class="col-1"></div>
|
||||
<div class="col-10">Name</div>
|
||||
<div class="col-4">Name</div>
|
||||
<div class="col-2">Price</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
|
|
@ -92,13 +130,34 @@
|
|||
>
|
||||
<div class="col-1">{{item.quantity}}</div>
|
||||
<div class="col-1">x</div>
|
||||
<div class="col-10">
|
||||
{{productOverview(props.row, item.product_id)}}
|
||||
<div class="col-4">
|
||||
{{productName(props.row, item.product_id)}}
|
||||
</div>
|
||||
<div class="col-2">
|
||||
{{productPrice(props.row, item.product_id)}}
|
||||
</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.row.extra.currency !== 'sat'"
|
||||
class="row items-center no-wrap q-mb-md q-mt-md"
|
||||
>
|
||||
<div class="col-3 q-pr-lg">Exchange Rate (1 BTC):</div>
|
||||
<div class="col-6 col-sm-8 q-pr-lg">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
disabled
|
||||
:value="formatFiat(props.row.extra.btc_price, props.row.extra.currency)"
|
||||
type="text"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-3 col-sm-1"></div>
|
||||
</div>
|
||||
<div class="row items-center no-wrap q-mb-md q-mt-md">
|
||||
<div class="col-3 q-pr-lg">Order ID:</div>
|
||||
<div class="col-6 col-sm-8 q-pr-lg">
|
||||
|
|
|
|||
|
|
@ -2,9 +2,18 @@ async function orderList(path) {
|
|||
const template = await loadTemplateAsync(path)
|
||||
Vue.component('order-list', {
|
||||
name: 'order-list',
|
||||
props: ['stall-id', 'adminkey', 'inkey'],
|
||||
props: ['stall-id', 'customer-pubkey-filter', 'adminkey', 'inkey'],
|
||||
template,
|
||||
|
||||
watch: {
|
||||
customerPubkeyFilter: async function (n) {
|
||||
this.search.publicKey = n
|
||||
this.search.isPaid = {label: 'All', id: null}
|
||||
this.search.isShipped = {label: 'All', id: null}
|
||||
await this.getOrders()
|
||||
}
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
orders: [],
|
||||
|
|
@ -12,6 +21,32 @@ async function orderList(path) {
|
|||
shippingMessage: '',
|
||||
showShipDialog: false,
|
||||
filter: '',
|
||||
search: {
|
||||
publicKey: '',
|
||||
isPaid: {
|
||||
label: 'All',
|
||||
id: null
|
||||
},
|
||||
isShipped: {
|
||||
label: 'All',
|
||||
id: null
|
||||
}
|
||||
},
|
||||
customers: [],
|
||||
ternaryOptions: [
|
||||
{
|
||||
label: 'All',
|
||||
id: null
|
||||
},
|
||||
{
|
||||
label: 'Yes',
|
||||
id: 'true'
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
id: 'false'
|
||||
}
|
||||
],
|
||||
ordersTable: {
|
||||
columns: [
|
||||
{
|
||||
|
|
@ -23,15 +58,21 @@ async function orderList(path) {
|
|||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: 'ID',
|
||||
label: 'Order ID',
|
||||
field: 'id'
|
||||
},
|
||||
{
|
||||
name: 'total',
|
||||
align: 'left',
|
||||
label: 'Total',
|
||||
label: 'Total Sats',
|
||||
field: 'total'
|
||||
},
|
||||
{
|
||||
name: 'fiat',
|
||||
align: 'left',
|
||||
label: 'Total Fiat',
|
||||
field: 'fiat'
|
||||
},
|
||||
{
|
||||
name: 'paid',
|
||||
align: 'left',
|
||||
|
|
@ -73,21 +114,51 @@ async function orderList(path) {
|
|||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
},
|
||||
productOverview: function (order, productId) {
|
||||
satBtc(val, showUnit = true) {
|
||||
return satOrBtc(val, showUnit, true)
|
||||
},
|
||||
formatFiat(value, currency) {
|
||||
return Math.trunc(value) + ' ' + currency
|
||||
},
|
||||
productName: function (order, productId) {
|
||||
product = order.extra.products.find(p => p.id === productId)
|
||||
if (product) {
|
||||
return `${product.name} (${product.price} ${order.extra.currency})`
|
||||
return product.name
|
||||
}
|
||||
return ''
|
||||
},
|
||||
productPrice: function (order, productId) {
|
||||
product = order.extra.products.find(p => p.id === productId)
|
||||
if (product) {
|
||||
return `${product.price} ${order.extra.currency}`
|
||||
}
|
||||
return ''
|
||||
},
|
||||
orderTotal: function (order) {
|
||||
return order.items.reduce((t, item) => {
|
||||
product = order.extra.products.find(p => p.id === item.product_id)
|
||||
return t + item.quantity * product.price
|
||||
}, 0)
|
||||
},
|
||||
getOrders: async function () {
|
||||
try {
|
||||
const ordersPath = this.stallId
|
||||
? `/stall/order/${this.stallId}`
|
||||
: '/order'
|
||||
? `stall/order/${this.stallId}`
|
||||
: 'order'
|
||||
|
||||
const query = []
|
||||
if (this.search.publicKey) {
|
||||
query.push(`pubkey=${this.search.publicKey}`)
|
||||
}
|
||||
if (this.search.isPaid.id) {
|
||||
query.push(`paid=${this.search.isPaid.id}`)
|
||||
}
|
||||
if (this.search.isShipped.id) {
|
||||
query.push(`shipped=${this.search.isShipped.id}`)
|
||||
}
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1' + ordersPath,
|
||||
`/nostrmarket/api/v1/${ordersPath}?${query.join('&')}`,
|
||||
this.inkey
|
||||
)
|
||||
this.orders = data.map(s => ({...s, expanded: false}))
|
||||
|
|
@ -95,6 +166,18 @@ async function orderList(path) {
|
|||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
getOrder: async function (orderId) {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
`/nostrmarket/api/v1/order/${orderId}`,
|
||||
this.inkey
|
||||
)
|
||||
return {...data, expanded: false, isNew: true}
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
updateOrderShipped: async function () {
|
||||
this.selectedOrder.shipped = !this.selectedOrder.shipped
|
||||
try {
|
||||
|
|
@ -117,6 +200,16 @@ async function orderList(path) {
|
|||
}
|
||||
this.showShipDialog = false
|
||||
},
|
||||
addOrder: async function (data) {
|
||||
if (
|
||||
!this.search.publicKey ||
|
||||
this.search.publicKey === data.customerPubkey
|
||||
) {
|
||||
const order = await this.getOrder(data.orderId)
|
||||
this.orders.unshift(order)
|
||||
}
|
||||
},
|
||||
|
||||
showShipOrderDialog: function (order) {
|
||||
this.selectedOrder = order
|
||||
this.shippingMessage = order.shipped
|
||||
|
|
@ -129,10 +222,35 @@ async function orderList(path) {
|
|||
},
|
||||
customerSelected: function (customerPubkey) {
|
||||
this.$emit('customer-selected', customerPubkey)
|
||||
},
|
||||
getCustomers: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1/customers',
|
||||
this.inkey
|
||||
)
|
||||
this.customers = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
buildCustomerLabel: function (c) {
|
||||
let label = `${c.profile.name || 'unknown'} ${c.profile.about || ''}`
|
||||
if (c.unread_messages) {
|
||||
label += `[new: ${c.unread_messages}]`
|
||||
}
|
||||
label += ` (${c.public_key.slice(0, 16)}...${c.public_key.slice(
|
||||
c.public_key.length - 16
|
||||
)}`
|
||||
return label
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getOrders()
|
||||
if (this.stallId) {
|
||||
await this.getOrders()
|
||||
}
|
||||
await this.getCustomers()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const merchant = async () => {
|
|||
merchant: {},
|
||||
shippingZones: [],
|
||||
activeChatCustomer: '',
|
||||
orderPubkey: null,
|
||||
showKeys: false,
|
||||
importKeyDialog: {
|
||||
show: false,
|
||||
|
|
@ -102,10 +103,43 @@ const merchant = async () => {
|
|||
},
|
||||
customerSelectedForOrder: function (customerPubkey) {
|
||||
this.activeChatCustomer = customerPubkey
|
||||
},
|
||||
filterOrdersForCustomer: function (customerPubkey) {
|
||||
this.orderPubkey = customerPubkey
|
||||
},
|
||||
waitForNotifications: async function () {
|
||||
try {
|
||||
const scheme = location.protocol === 'http:' ? 'ws' : 'wss'
|
||||
const port = location.port ? `:${location.port}` : ''
|
||||
const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}`
|
||||
const wsConnection = new WebSocket(wsUrl)
|
||||
wsConnection.onmessage = async e => {
|
||||
const data = JSON.parse(e.data)
|
||||
if (data.type === 'new-order') {
|
||||
this.$q.notify({
|
||||
timeout: 5000,
|
||||
type: 'positive',
|
||||
message: 'New Order'
|
||||
})
|
||||
await this.$refs.orderListRef.addOrder(data)
|
||||
} else if (data.type === 'new-customer') {
|
||||
} else if (data.type === 'new-direct-message') {
|
||||
await this.$refs.directMessagesRef.handleNewMessage(data)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
timeout: 5000,
|
||||
type: 'warning',
|
||||
message: 'Failed to watch for updated',
|
||||
caption: `${error}`
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getMerchant()
|
||||
await this.waitForNotifications()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ function isJson(str) {
|
|||
}
|
||||
}
|
||||
|
||||
function satOrBtc(val, showUnit = true, showSats = false) {
|
||||
const value = showSats
|
||||
? LNbits.utils.formatSat(val)
|
||||
: val == 0
|
||||
? 0.0
|
||||
: (val / 100000000).toFixed(8)
|
||||
if (!showUnit) return value
|
||||
return showSats ? value + ' sat' : value + ' BTC'
|
||||
}
|
||||
|
||||
function timeFromNow(time) {
|
||||
// Get timestamps
|
||||
let unixTime = new Date(time).getTime()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue