mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-26 23:51:55 +01:00
2339 lines
68 KiB
HTML
2339 lines
68 KiB
HTML
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
%} {% block page %}
|
|
<div class="row q-col-gutter-md">
|
|
<!-- PRODUCT DIALOG -->
|
|
<q-dialog v-model="productDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
<q-form @submit="sendProductFormData" class="q-gutter-md">
|
|
<q-select
|
|
filled
|
|
dense
|
|
emit-value
|
|
v-model="productDialog.data.stall"
|
|
:options="stalls.map(s => ({label: s.name, value: s.id}))"
|
|
label="Stall"
|
|
>
|
|
</q-select>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="productDialog.data.product"
|
|
label="Product"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="productDialog.data.description"
|
|
label="Description"
|
|
></q-input>
|
|
<!-- <div class="row"> -->
|
|
<!-- <div class="col-5">
|
|
<q-select
|
|
filled
|
|
dense
|
|
multiple
|
|
v-model.trim="productDialog.data.categories"
|
|
:options="categories"
|
|
label="Categories"
|
|
class="q-pr-sm"
|
|
></q-select>
|
|
</div> -->
|
|
<!-- <div class="col-7"> -->
|
|
<q-select
|
|
filled
|
|
multiple
|
|
dense
|
|
emit-value
|
|
v-model.trim="productDialog.data.categories"
|
|
use-input
|
|
use-chips
|
|
multiple
|
|
hide-dropdown-icon
|
|
input-debounce="0"
|
|
new-value-mode="add-unique"
|
|
label="Categories"
|
|
placeholder="crafts,robots,etc"
|
|
hint="Hit Enter to add"
|
|
></q-select>
|
|
<!-- </div> -->
|
|
<!-- </div> -->
|
|
|
|
<q-file
|
|
class="q-pr-md"
|
|
filled
|
|
dense
|
|
capture="environment"
|
|
accept="image/jpeg, image/png"
|
|
:max-file-size="3*1024**2"
|
|
label="Small image (optional)"
|
|
clearable
|
|
@input="imageAdded"
|
|
@clear="imageCleared"
|
|
>
|
|
<template v-if="productDialog.data.image" v-slot:before>
|
|
<img style="height: 1em" :src="productDialog.data.image" />
|
|
</template>
|
|
<template v-if="productDialog.data.image" v-slot:append>
|
|
<q-icon
|
|
name="cancel"
|
|
@click.stop.prevent="imageCleared"
|
|
class="cursor-pointer"
|
|
/>
|
|
</template>
|
|
</q-file>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="productDialog.data.price"
|
|
type="number"
|
|
label="Price"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="productDialog.data.quantity"
|
|
type="number"
|
|
label="Quantity"
|
|
></q-input>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="productDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Product</q-btn
|
|
>
|
|
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="productDialog.data.price == null
|
|
|| productDialog.data.product == null
|
|
|| productDialog.data.description == null
|
|
|| productDialog.data.quantity == null"
|
|
type="submit"
|
|
>Create Product</q-btn
|
|
>
|
|
|
|
<q-btn
|
|
v-close-popup
|
|
flat
|
|
@click="resetDialog('productDialog')"
|
|
color="grey"
|
|
class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
<!-- ZONE DIALOG -->
|
|
<q-dialog v-model="zoneDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
<q-form @submit="sendZoneFormData" class="q-gutter-md">
|
|
<q-select
|
|
filled
|
|
dense
|
|
multiple
|
|
:options="shippingZoneOptions"
|
|
label="Countries"
|
|
v-model.trim="zoneDialog.data.countries"
|
|
></q-select>
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
v-model.trim="zoneDialog.data.cost"
|
|
label="Cost (sats)"
|
|
></q-input>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="zoneDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Shipping Zone</q-btn
|
|
>
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="zoneDialog.data.countries == null
|
|
|| zoneDialog.data.cost == null"
|
|
type="submit"
|
|
>Create Shipping Zone</q-btn
|
|
>
|
|
|
|
<q-btn
|
|
v-close-popup
|
|
flat
|
|
@click="resetDialog('zoneDialog')"
|
|
color="grey"
|
|
class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
<!-- MARKETPLACE/SHOP DIALOG -->
|
|
<q-dialog v-model="marketDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
<q-form @submit="sendMarketplaceFormData" class="q-gutter-md">
|
|
<q-toggle
|
|
label="Activate marketplace"
|
|
color="primary"
|
|
v-model="marketDialog.data.activate"
|
|
></q-toggle>
|
|
<q-select
|
|
filled
|
|
dense
|
|
multiple
|
|
emit-value
|
|
:options="stalls.map(s => ({label: s.name, value: s.id}))"
|
|
label="Stalls"
|
|
v-model.trim="marketDialog.data.stalls"
|
|
></q-select>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="marketDialog.data.name"
|
|
label="Name"
|
|
></q-input>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="marketDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Marketplace</q-btn
|
|
>
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="marketDialog.data.activate == null
|
|
|| marketDialog.data.stalls == null"
|
|
type="submit"
|
|
>Launch Marketplace</q-btn
|
|
>
|
|
|
|
<q-btn
|
|
v-close-popup
|
|
flat
|
|
@click="resetDialog('marketDialog')"
|
|
color="grey"
|
|
class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
<!-- STALL/STORE DIALOG -->
|
|
<q-dialog v-model="stallDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
<q-form @submit="sendStallFormData" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="stallDialog.data.name"
|
|
label="Name"
|
|
></q-input>
|
|
<q-select
|
|
filled
|
|
dense
|
|
emit-value
|
|
v-model="stallDialog.data.wallet"
|
|
:options="g.user.walletOptions"
|
|
label="Wallet *"
|
|
>
|
|
</q-select>
|
|
<q-input
|
|
v-if="keys"
|
|
filled
|
|
dense
|
|
v-model.trim="stallDialog.data.publickey"
|
|
label="Public Key"
|
|
></q-input>
|
|
<q-input
|
|
v-if="keys"
|
|
filled
|
|
dense
|
|
v-model.trim="stallDialog.data.privatekey"
|
|
label="Private Key"
|
|
></q-input>
|
|
<!-- NOSTR -->
|
|
<div class="row">
|
|
<div class="col-5">
|
|
<q-btn unelevated @click="generateKeys" color="primary"
|
|
>Generate keys</q-btn
|
|
>
|
|
</div>
|
|
<div class="col-5">
|
|
<q-btn unelevated @click="restoreKeys" color="primary"
|
|
>Restore keys</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-select
|
|
:options="zoneOptions"
|
|
filled
|
|
dense
|
|
multiple
|
|
v-model.trim="stallDialog.data.shippingzones"
|
|
label="Shipping Zones"
|
|
></q-select>
|
|
<!-- NOSTR -->
|
|
<!-- <q-select
|
|
:options="relayOptions"
|
|
filled
|
|
dense
|
|
multiple
|
|
v-model.trim="stallDialog.data.relays"
|
|
label="Relays"
|
|
></q-select>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="stallDialog.data.crelays"
|
|
label="Custom relays (seperate by comma)"
|
|
></q-input>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="stallDialog.data.nostrShops"
|
|
label="Nostr shop public keys (seperate by comma)"
|
|
></q-input> -->
|
|
<p>
|
|
<strong><small>Nostr support coming soon!</small></strong>
|
|
</p>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="stallDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Stall</q-btn
|
|
>
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="stallDialog.data.wallet == null
|
|
|| stallDialog.data.shippingzones == null
|
|
|| stallDialog.data.publickey == null
|
|
|| stallDialog.data.privatekey == null"
|
|
type="submit"
|
|
>Create Stall</q-btn
|
|
>
|
|
<q-btn
|
|
v-close-popup
|
|
flat
|
|
@click="resetDialog('stallDialog')"
|
|
color="grey"
|
|
class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-btn unelevated color="primary" @click="zoneDialog.show = true"
|
|
>+ Shipping Zone<q-tooltip> Create a shipping zone </q-tooltip></q-btn
|
|
>
|
|
<q-btn
|
|
unelevated
|
|
v-if="zones.length > 0"
|
|
color="primary"
|
|
@click="openStallDialog()"
|
|
>+ Stall
|
|
<q-tooltip>
|
|
Create a market stall to list products on
|
|
</q-tooltip></q-btn
|
|
>
|
|
<q-btn
|
|
unelevated
|
|
v-else
|
|
color="primary"
|
|
@click="errorMessage('First set shipping zone(s).')"
|
|
>+ Stall
|
|
<q-tooltip>
|
|
Create a market stall to list products on
|
|
</q-tooltip></q-btn
|
|
>
|
|
<q-btn
|
|
unelevated
|
|
v-if="stalls.length > 0"
|
|
color="primary"
|
|
@click="productDialog.show = true"
|
|
>+ Product <q-tooltip> List a product </q-tooltip></q-btn
|
|
>
|
|
<q-btn
|
|
unelevated
|
|
v-else
|
|
color="primary"
|
|
@click="errorMessage('First set shipping zone(s), then create a stall.')"
|
|
>+ Product <q-tooltip> List a product </q-tooltip></q-btn
|
|
>
|
|
<q-btn
|
|
class="float-right"
|
|
unelevated
|
|
flat
|
|
color="primary"
|
|
@click="marketDialog.show = true"
|
|
>Create Market
|
|
<q-tooltip>
|
|
Makes a simple frontend shop for your stalls (not NOSTR)</q-tooltip
|
|
></q-btn
|
|
>
|
|
</q-card-section>
|
|
<q-separator inset></q-separator>
|
|
<q-card-section>
|
|
<div class="text-h6">Shop</div>
|
|
<div class="text-subtitle2">
|
|
Step inside the Leaky Cauldron and enter the Shop. Make this
|
|
market available on Nostr!
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<q-card-section>
|
|
<q-toggle
|
|
disable
|
|
v-model="diagonAlley"
|
|
checked-icon="check"
|
|
color="green"
|
|
unchecked-icon="clear"
|
|
label='"Shop" mode (Nostr)'
|
|
@input="toggleDA"
|
|
>
|
|
<q-tooltip>Coming soon...</q-tooltip></q-toggle
|
|
>
|
|
<q-btn
|
|
disable
|
|
class="float-right"
|
|
unelevated
|
|
color="primary"
|
|
@click="shopDataDownload"
|
|
>Download Data
|
|
<q-tooltip>
|
|
Download all data (shops, products, orders, etc...)</q-tooltip
|
|
></q-btn
|
|
>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<!-- ORDERS TABLE -->
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Orders</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportOrdersCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:data="orders"
|
|
row-key="id"
|
|
:columns="ordersTable.columns"
|
|
:pagination.sync="ordersTable.pagination"
|
|
>
|
|
{% raw %}
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
size="sm"
|
|
color="accent"
|
|
round
|
|
dense
|
|
@click="props.expand = !props.expand"
|
|
:icon="props.expand ? 'remove' : 'add'"
|
|
/>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
size="sm"
|
|
color="green"
|
|
dense
|
|
icon="chat"
|
|
@click="chatRoom(props.row.invoiceid)"
|
|
>
|
|
<q-badge
|
|
v-if="props.row.unread"
|
|
color="red"
|
|
rounded
|
|
floating
|
|
style="padding: 6px; border-radius: 6px"
|
|
/>
|
|
</q-btn>
|
|
</q-td>
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.value }}
|
|
</q-td>
|
|
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="shipOrder(props.row.id)"
|
|
icon="add_shopping_cart"
|
|
color="green"
|
|
>
|
|
<q-tooltip> Product shipped? </q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteOrder(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
<q-tr v-show="props.expand" :props="props">
|
|
<q-td colspan="100%">
|
|
<template>
|
|
<div class="q-pa-md">
|
|
<q-list>
|
|
<q-item-label header>Order Details</q-item-label>
|
|
|
|
<q-item v-for="col in props.row.details" :key="col.id">
|
|
<q-item-section>
|
|
<q-item-label>Products</q-item-label>
|
|
<q-item-label caption
|
|
>{{ products.length && (_.findWhere(products, {id:
|
|
col.product_id})).product }}</q-item-label
|
|
>
|
|
<q-item-label caption
|
|
>Quantity: {{ col.quantity }}</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label>Shipping to</q-item-label>
|
|
<q-item-label caption
|
|
>{{ props.row.address }}</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label>User info</q-item-label>
|
|
<q-item-label caption v-if="props.row.username"
|
|
>{{ props.row.username }}</q-item-label
|
|
>
|
|
<q-item-label caption
|
|
>{{ props.row.email }}</q-item-label
|
|
>
|
|
<q-item-label caption v-if="props.row.pubkey"
|
|
>{{ props.row.pubkey }}</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label>Total</q-item-label>
|
|
<q-item-label>{{ props.row.total }}</q-item-label>
|
|
<!-- <q-icon name="star" color="yellow" /> -->
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
</template>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
{% endraw %}
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<!-- PRODUCTS TABLE -->
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Products</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportProductsCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:data="products"
|
|
row-key="id"
|
|
:columns="productsTable.columns"
|
|
:pagination.sync="productsTable.pagination"
|
|
>
|
|
{% raw %}
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
disabled
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="add_shopping_cart"
|
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="props.row.wallet"
|
|
target="_blank"
|
|
></q-btn>
|
|
<q-tooltip> Link to pass to stall relay </q-tooltip>
|
|
</q-td>
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.value }}
|
|
</q-td>
|
|
<q-td class="text-center" auto-width>
|
|
<img
|
|
v-if="props.row.image"
|
|
:src="props.row.image"
|
|
style="height: 1.5em"
|
|
/>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="openProductUpdateDialog(props.row.id)"
|
|
icon="edit"
|
|
color="light-blue"
|
|
></q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteProduct(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
{% endraw %}
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<!-- STALLS TABLE -->
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Market Stalls</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportStallsCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:data="stalls"
|
|
row-key="id"
|
|
:columns="stallTable.columns"
|
|
:pagination.sync="stallTable.pagination"
|
|
>
|
|
{% raw %}
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="storefront"
|
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="'/shop/stalls/' + props.row.id"
|
|
target="_blank"
|
|
></q-btn>
|
|
<q-tooltip> Stall simple UI shopping cart </q-tooltip>
|
|
</q-td>
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.value }}
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="openStallUpdateDialog(props.row.id)"
|
|
icon="edit"
|
|
color="light-blue"
|
|
></q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteStall(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
{% endraw %}
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card v-if="markets.length">
|
|
<!-- MARKETPLACES TABLE -->
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Marketplaces</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportStallsCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:data="markets"
|
|
row-key="id"
|
|
:columns="marketTable.columns"
|
|
:pagination.sync="marketTable.pagination"
|
|
>
|
|
{% raw %}
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="storefront"
|
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="'/shop/market/' + props.row.id"
|
|
target="_blank"
|
|
></q-btn>
|
|
<q-tooltip> Link to pass to stall relay </q-tooltip>
|
|
</q-td>
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.value }}
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="openStallUpdateDialog(props.row.id)"
|
|
icon="edit"
|
|
color="light-blue"
|
|
></q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteStall(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
{% endraw %}
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<!-- ZONES TABLE -->
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Shipping Zones</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportZonesCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:data="zones"
|
|
row-key="id"
|
|
:columns="zonesTable.columns"
|
|
:pagination.sync="zonesTable.pagination"
|
|
>
|
|
{% raw %}
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.value }}
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="openZoneUpdateDialog(props.row.id)"
|
|
icon="edit"
|
|
color="light-blue"
|
|
></q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteZone(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
{% endraw %}
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
<!-- KEYS -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Keys</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportKeysCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<div v-if="keys" class="row">
|
|
<div
|
|
class="col-6"
|
|
v-for="type in ['pubkey', 'privkey']"
|
|
v-bind:key="type"
|
|
>
|
|
<div class="text-center q-mb-lg">
|
|
{% raw %}
|
|
<q-responsive
|
|
:ratio="1"
|
|
class="q-mx-xl"
|
|
@click="copyText(keys[type])"
|
|
>
|
|
<qrcode
|
|
:value="keys[type]"
|
|
:options="{width: 250}"
|
|
class="rounded-borders"
|
|
></qrcode>
|
|
<q-tooltip>{{ keys[type] }}</q-tooltip>
|
|
</q-responsive>
|
|
<p>
|
|
{{ type == 'pubkey' ? 'Public Key' : 'Private Key' }}<br /><small
|
|
>Click to copy</small
|
|
>
|
|
</p>
|
|
{% endraw %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 q-my-none">
|
|
LNbits Shop Extension, powered by Nostr
|
|
</h6>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
<q-list> {% include "shop/_api_docs.html" %} </q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
<!-- CHAT BOX -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<q-select
|
|
v-model="customerKey"
|
|
:options="Object.keys(messages).map(k => ({label: `${k.slice(0, 25)}...`, value: k}))"
|
|
label="Customers"
|
|
@input="chatRoom(customerKey)"
|
|
emit-value
|
|
></q-select>
|
|
</q-card-section>
|
|
<div class="chat-container q-pa-md" ref="chatCard">
|
|
<div class="chat-box">
|
|
<!-- <p v-if="Object.keys(messages).length === 0">No messages yet</p> -->
|
|
<div class="chat-messages">
|
|
<q-chat-message
|
|
:key="index"
|
|
v-for="(message, index) in orderMessages"
|
|
:name="message.pubkey == keys.pubkey ? 'me' : 'customer'"
|
|
:text="[message.msg]"
|
|
:sent="message.pubkey == keys.pubkey ? true : false"
|
|
:bg-color="message.pubkey == keys.pubkey ? 'white' : 'light-green-2'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<q-form @submit="sendMessage" class="full-width chat-input">
|
|
<q-input
|
|
ref="newMessage"
|
|
v-model="newMessage"
|
|
placeholder="Message"
|
|
class="full-width"
|
|
dense
|
|
outlined
|
|
>
|
|
<template>
|
|
<q-btn
|
|
round
|
|
dense
|
|
flat
|
|
type="submit"
|
|
icon="send"
|
|
color="primary"
|
|
/>
|
|
</template>
|
|
</q-input>
|
|
</q-form>
|
|
</div>
|
|
</q-card>
|
|
<!-- <q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
<div
|
|
ref="chatCard"
|
|
class="q-ma-md q-pb-lg"
|
|
style="height: 350px"
|
|
>
|
|
<div class="q-pb-md">
|
|
<q-select
|
|
v-model="customerKey"
|
|
style="width: 80%"
|
|
:options="Object.keys(messages)"
|
|
label="Customers"
|
|
@input="chatRoom(customerKey)"
|
|
></q-select>
|
|
<div class="chat-container q-pa-md">
|
|
<div class="chat-box">
|
|
<p v-if="Object.keys(messages).length === 0">No messages yet</p>
|
|
<div class="chat-messages">
|
|
<q-chat-message
|
|
:key="index"
|
|
v-for="(message, index) in orderMessages"
|
|
:name="message.pubkey == keys.pubkey ? 'me' : 'customer'"
|
|
:text="[message.msg]"
|
|
:sent="message.pubkey == keys.pubkey ? true : false"
|
|
:bg-color="message.pubkey == keys.pubkey ? 'white' : 'light-green-2'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<q-form @submit="sendMessage" class="full-width chat-input">
|
|
<q-input
|
|
ref="newMessage"
|
|
v-model="newMessage"
|
|
placeholder="Message"
|
|
class="full-width"
|
|
dense
|
|
outlined
|
|
>
|
|
<template>
|
|
<q-btn
|
|
round
|
|
dense
|
|
flat
|
|
type="submit"
|
|
icon="send"
|
|
color="primary"
|
|
/>
|
|
</template>
|
|
</q-input>
|
|
</q-form>
|
|
</div>
|
|
</div>
|
|
<div class="col-8 q-px-md">
|
|
<div v-for="message in customerMessages">
|
|
<q-chat-message
|
|
v-if="message[0] == 'out'"
|
|
:text="[message[1]]"
|
|
sent
|
|
></q-chat-message>
|
|
<q-chat-message v-else :text="[message[1]]"></q-chat-message>
|
|
</div>
|
|
</div>
|
|
<div class="col on-right" style="width: 90%">
|
|
<q-input>
|
|
<template v-slot:after>
|
|
<q-btn round dense flat icon="send" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card> -->
|
|
</div>
|
|
<q-dialog v-model="onboarding.show">
|
|
<q-card class="q-pa-lg">
|
|
<h6 class="q-my-md text-primary">How to use Shop</h6>
|
|
<q-stepper v-model="step" color="primary" vertical animated>
|
|
<q-step
|
|
:name="1"
|
|
title="Create a Shipping Zone"
|
|
icon="settings"
|
|
:done="step > 1"
|
|
>
|
|
Create Shipping Zones you're willing to ship to. You can define
|
|
different values for different zones.
|
|
<q-stepper-navigation>
|
|
<q-btn @click="step = step + 1" color="primary" label="Next" />
|
|
</q-stepper-navigation>
|
|
</q-step>
|
|
<q-step
|
|
:name="2"
|
|
title="Create a Stall"
|
|
icon="create_new_folder"
|
|
:done="step > 2"
|
|
>
|
|
Create a Stall and provide private and public keys to use for
|
|
communication. If you don't have one, LNbits will create a key pair
|
|
for you. It will be saved and can be used on other stalls.
|
|
<q-stepper-navigation>
|
|
<q-btn @click="step = step + 1" color="primary" label="Next" />
|
|
</q-stepper-navigation>
|
|
</q-step>
|
|
|
|
<q-step :name="3" title="Create Products" icon="assignment">
|
|
Create your products, add a small description and an image. Choose to
|
|
what stall, if you have more than one, it belongs to
|
|
<q-stepper-navigation>
|
|
<q-btn @click="onboarding.finish" color="primary" label="Finish" />
|
|
</q-stepper-navigation>
|
|
<div>
|
|
<q-checkbox
|
|
v-model="onboarding.showAgain"
|
|
label="Show this again?"
|
|
/>
|
|
</div>
|
|
</q-step>
|
|
</q-stepper>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
|
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
<script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script>
|
|
|
|
<script>
|
|
Vue.component(VueQrcode.name, VueQrcode)
|
|
|
|
const pica = window.pica()
|
|
|
|
function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
|
|
let ratio = Math.min(
|
|
1,
|
|
maxWidth / img.naturalWidth,
|
|
maxHeight / img.naturalHeight
|
|
)
|
|
return {width: img.naturalWidth * ratio, height: img.naturalHeight * ratio}
|
|
}
|
|
|
|
const mapStalls = obj => {
|
|
obj._data = _.clone(obj)
|
|
return obj
|
|
}
|
|
const mapProducts = obj => {
|
|
obj._data = _.clone(obj)
|
|
return obj
|
|
}
|
|
const mapZone = obj => {
|
|
obj._data = _.clone(obj)
|
|
return obj
|
|
}
|
|
const mapOrders = obj => {
|
|
obj._data = _.clone(obj)
|
|
obj.time = Quasar.utils.date.formatDate(
|
|
new Date(obj.time * 1000),
|
|
'YYYY-MM-DD HH:mm'
|
|
)
|
|
// obj.unread = false
|
|
return obj
|
|
}
|
|
const mapKeys = obj => {
|
|
obj._data = _.clone(obj)
|
|
return obj
|
|
}
|
|
|
|
const mapMarkets = obj => {
|
|
obj._data = _.clone(obj)
|
|
obj.stores = []
|
|
LNbits.api
|
|
.request('GET', `/shop/api/v1/markets/${obj.id}/stalls`, null)
|
|
.then(response => {
|
|
if (response.data) {
|
|
obj.stores = response.data.map(s => s.name).toString()
|
|
}
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
return obj
|
|
}
|
|
|
|
const humanReadableZones = zones => {
|
|
return zones.map(z => `${z.id} - ${z.countries}`)
|
|
}
|
|
|
|
new Vue({
|
|
el: '#vue',
|
|
mixins: [windowMixin],
|
|
data: function () {
|
|
return {
|
|
step: 1,
|
|
onboarding: {
|
|
show: true,
|
|
showAgain: false,
|
|
finish: () => {
|
|
this.$q.localStorage.set(
|
|
'lnbits.DAOnboarding',
|
|
this.onboarding.showAgain
|
|
)
|
|
this.onboarding.show = false
|
|
}
|
|
},
|
|
keys: null,
|
|
diagonAlley: false,
|
|
products: [],
|
|
orders: [],
|
|
stalls: [],
|
|
markets: [],
|
|
zones: [],
|
|
zoneOptions: [],
|
|
customerKeys: [],
|
|
customerKey: '',
|
|
customerMessages: {},
|
|
messages: {},
|
|
newMessage: '',
|
|
orderMessages: {},
|
|
shippedModel: false,
|
|
shippingZoneOptions: [
|
|
'Free (digital)',
|
|
'Worldwide',
|
|
'Europe',
|
|
'Australia',
|
|
'Austria',
|
|
'Belgium',
|
|
'Brazil',
|
|
'Canada',
|
|
'Denmark',
|
|
'Finland',
|
|
'France',
|
|
'Germany',
|
|
'Greece',
|
|
'Hong Kong',
|
|
'Hungary',
|
|
'Ireland',
|
|
'Indonesia',
|
|
'Israel',
|
|
'Italy',
|
|
'Japan',
|
|
'Kazakhstan',
|
|
'Korea',
|
|
'Luxembourg',
|
|
'Malaysia',
|
|
'Mexico',
|
|
'Netherlands',
|
|
'New Zealand',
|
|
'Norway',
|
|
'Poland',
|
|
'Portugal',
|
|
'Russia',
|
|
'Saudi Arabia',
|
|
'Singapore',
|
|
'Spain',
|
|
'Sweden',
|
|
'Switzerland',
|
|
'Thailand',
|
|
'Turkey',
|
|
'Ukraine',
|
|
'United Kingdom**',
|
|
'United States***',
|
|
'Vietnam',
|
|
'China'
|
|
],
|
|
categories: [
|
|
'Fashion (clothing and accessories)',
|
|
'Health (and beauty)',
|
|
'Toys (and baby equipment)',
|
|
'Media (Books and CDs)',
|
|
'Groceries (Food and Drink)',
|
|
'Technology (Phones and Computers)',
|
|
'Home (furniture and accessories)',
|
|
'Gifts (flowers, cards, etc)',
|
|
'Adult',
|
|
'Other'
|
|
],
|
|
relayOptions: [
|
|
'wss://nostr-relay.herokuapp.com/ws',
|
|
'wss://nostr-relay.bigsun.xyz/ws',
|
|
'wss://freedom-relay.herokuapp.com/ws'
|
|
],
|
|
label: '',
|
|
ordersTable: {
|
|
columns: [
|
|
/*{
|
|
name: 'product',
|
|
align: 'left',
|
|
label: 'Product',
|
|
field: 'product'
|
|
},
|
|
{
|
|
name: 'quantity',
|
|
align: 'left',
|
|
label: 'Quantity',
|
|
field: 'quantity'
|
|
},*/
|
|
{
|
|
name: 'id',
|
|
align: 'left',
|
|
label: 'ID',
|
|
field: 'id'
|
|
},
|
|
{
|
|
name: 'time',
|
|
align: 'left',
|
|
label: 'Date',
|
|
field: 'time'
|
|
},
|
|
{
|
|
name: 'invoiceid',
|
|
align: 'left',
|
|
label: 'InvoiceID',
|
|
field: 'invoiceid'
|
|
},
|
|
{name: 'paid', align: 'left', label: 'Paid', field: 'paid'},
|
|
{name: 'shipped', align: 'left', label: 'Shipped', field: 'shipped'}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
productsTable: {
|
|
columns: [
|
|
{
|
|
name: 'stall',
|
|
align: 'left',
|
|
label: 'Stall',
|
|
field: 'stall'
|
|
},
|
|
{
|
|
name: 'product',
|
|
align: 'left',
|
|
label: 'Product',
|
|
field: 'product'
|
|
},
|
|
{
|
|
name: 'description',
|
|
align: 'left',
|
|
label: 'Description',
|
|
field: 'description'
|
|
},
|
|
{
|
|
name: 'categories',
|
|
align: 'left',
|
|
label: 'Categories',
|
|
field: 'categories'
|
|
},
|
|
{name: 'price', align: 'left', label: 'Price', field: 'price'},
|
|
{
|
|
name: 'quantity',
|
|
align: 'left',
|
|
label: 'Quantity',
|
|
field: 'quantity'
|
|
},
|
|
{name: 'id', align: 'left', label: 'ID', field: 'id'}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
stallTable: {
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
align: 'left',
|
|
label: 'ID',
|
|
field: 'id'
|
|
},
|
|
{
|
|
name: 'name',
|
|
align: 'left',
|
|
label: 'Name',
|
|
field: 'name'
|
|
},
|
|
{
|
|
name: 'wallet',
|
|
align: 'left',
|
|
label: 'Wallet',
|
|
field: 'wallet'
|
|
},
|
|
{
|
|
name: 'publickey',
|
|
align: 'left',
|
|
label: 'Public key',
|
|
field: 'publickey'
|
|
},
|
|
{
|
|
name: 'privatekey',
|
|
align: 'left',
|
|
label: 'Private key',
|
|
field: 'privatekey'
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
marketTable: {
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
align: 'left',
|
|
label: 'ID',
|
|
field: 'id'
|
|
},
|
|
{
|
|
name: 'name',
|
|
align: 'left',
|
|
label: 'Name',
|
|
field: 'name'
|
|
},
|
|
{
|
|
name: 'stores',
|
|
align: 'left',
|
|
label: 'Stalls',
|
|
field: 'stores'
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
zonesTable: {
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
align: 'left',
|
|
label: 'ID',
|
|
field: 'id'
|
|
},
|
|
{
|
|
name: 'countries',
|
|
align: 'left',
|
|
label: 'Countries',
|
|
field: 'countries'
|
|
},
|
|
{
|
|
name: 'cost',
|
|
align: 'left',
|
|
label: 'Cost',
|
|
field: 'cost'
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
productDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
stallDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
zoneDialog: {
|
|
show: false,
|
|
data: {countries: []}
|
|
},
|
|
marketDialog: {
|
|
show: false,
|
|
data: {activate: false}
|
|
},
|
|
orderDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
relayDialog: {
|
|
show: false,
|
|
data: {}
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
categoryOther: function () {
|
|
cats = trim(this.productDialog.data.categories.split(','))
|
|
for (let i = 0; i < cats.length; i++) {
|
|
if (cats[i] == 'Others') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
},
|
|
methods: {
|
|
resetDialog(dialog) {
|
|
this[dialog].show = false
|
|
this[dialog].data = {}
|
|
},
|
|
toggleDA(value, evt) {
|
|
this.$q.localStorage.set(`lnbits.DAmode`, value)
|
|
},
|
|
shopDataDownload() {
|
|
const removeClone = obj => {
|
|
delete obj._data
|
|
return obj
|
|
}
|
|
let data = {
|
|
orders: this.orders.map(removeClone),
|
|
stalls: this.stalls.map(removeClone),
|
|
products: this.products.map(removeClone),
|
|
keys: this.keys
|
|
}
|
|
const file = new File([JSON.stringify(data)], 'backup_data.json', {
|
|
type: 'text/json'
|
|
})
|
|
const link = document.createElement('a')
|
|
const url = URL.createObjectURL(file)
|
|
|
|
link.href = url
|
|
link.download = file.name
|
|
link.click()
|
|
|
|
window.URL.revokeObjectURL(url)
|
|
},
|
|
generateKeys() {
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/keys',
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
.then(response => {
|
|
if (response.data) {
|
|
this.keys = response.data
|
|
this.stallDialog.data.publickey = this.keys.pubkey
|
|
this.stallDialog.data.privatekey = this.keys.privkey
|
|
this.$q.localStorage.set(
|
|
`lnbits.shop.${this.g.user.id}`,
|
|
this.keys
|
|
)
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
restoreKeys() {
|
|
let keys = this.$q.localStorage.getItem(
|
|
`lnbits.shop.${this.g.user.id}`
|
|
)
|
|
if (keys) {
|
|
this.keys = keys
|
|
this.stallDialog.data.publickey = this.keys.pubkey
|
|
this.stallDialog.data.privatekey = this.keys.privkey
|
|
} else {
|
|
this.$q.notify({
|
|
type: 'warning',
|
|
message: 'No keys found.'
|
|
})
|
|
}
|
|
},
|
|
exportKeysCSV: function () {
|
|
let colls = [
|
|
{
|
|
name: 'privatekey',
|
|
align: 'left',
|
|
label: 'Private Key',
|
|
field: 'privkey'
|
|
},
|
|
{
|
|
name: 'publickey',
|
|
align: 'left',
|
|
label: 'Public Key',
|
|
field: 'pubkey'
|
|
}
|
|
]
|
|
LNbits.utils.exportCSV(colls, [this.keys])
|
|
},
|
|
capitalizeFirstLetter: function (string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
},
|
|
errorMessage: function (error) {
|
|
this.$q.notify({
|
|
color: 'primary',
|
|
message: error
|
|
})
|
|
},
|
|
////////////////////////////////////////
|
|
///////////SUPPORT MESSAGES/////////////
|
|
////////////////////////////////////////
|
|
getMessages: function (customerKey) {
|
|
var self = this
|
|
messages = []
|
|
messages.push(['in', 'blah blah'])
|
|
messages.push(['out', 'blah blah'])
|
|
self.customerMessages = messages
|
|
},
|
|
////////////////////////////////////////
|
|
////////////////STALLS//////////////////
|
|
////////////////////////////////////////
|
|
getStalls: function () {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/stalls?all_wallets=true',
|
|
self.g.user.wallets[0].adminkey
|
|
)
|
|
.then(function (response) {
|
|
if (response.data) {
|
|
self.stalls = response.data.map(mapStalls)
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
openStallDialog: function () {
|
|
this.zoneOptions = humanReadableZones(this.zones)
|
|
this.stallDialog.show = true
|
|
},
|
|
openStallUpdateDialog: function (linkId) {
|
|
var self = this
|
|
var link = _.findWhere(self.stalls, {id: linkId})
|
|
this.zoneOptions = humanReadableZones(this.zones)
|
|
this.stallDialog.data = _.clone(link._data)
|
|
let shippingzones = this.zoneOptions.filter(z =>
|
|
this.stallDialog.data.shippingzones
|
|
.split(',')
|
|
.includes(z.split('-')[0].trim())
|
|
)
|
|
|
|
this.stallDialog.data.shippingzones = shippingzones //this.stallDialog.data.shippingzones.split(",")
|
|
|
|
//let zones = this.zoneOptions
|
|
// .filter(z => z.id == )
|
|
this.stallDialog.show = true
|
|
},
|
|
sendStallFormData: function () {
|
|
let data = {
|
|
name: this.stallDialog.data.name,
|
|
wallet: this.stallDialog.data.wallet,
|
|
publickey: this.stallDialog.data.publickey,
|
|
privatekey: this.stallDialog.data.privatekey,
|
|
relays: this.stallDialog.data.relays,
|
|
shippingzones: this.stallDialog.data.shippingzones
|
|
.map(z => z.split('-')[0].trim())
|
|
.toString()
|
|
}
|
|
if (this.stallDialog.data.id) {
|
|
this.stallDialog.data = {...this.stallDialog.data, ...data}
|
|
this.updateStall(this.stallDialog.data)
|
|
} else {
|
|
this.createStall(data)
|
|
}
|
|
},
|
|
updateStall: function (data) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'PUT',
|
|
'/shop/api/v1/stalls/' + data.id,
|
|
_.findWhere(self.g.user.wallets, {
|
|
id: self.stallDialog.data.wallet
|
|
}).inkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.stalls = _.reject(self.stalls, function (obj) {
|
|
return obj.id == data.id
|
|
})
|
|
self.stalls.push(mapStalls(response.data))
|
|
self.resetDialog('stallDialog')
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
createStall: function (data) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/stalls',
|
|
_.findWhere(self.g.user.wallets, {
|
|
id: self.stallDialog.data.wallet
|
|
}).inkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.stalls.push(mapStalls(response.data))
|
|
self.resetDialog('stallDialog')
|
|
//self.stallDialog.show = false
|
|
//self.stallDialog.data = {}
|
|
//data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
deleteStall: function (stallId) {
|
|
var self = this
|
|
var stall = _.findWhere(self.stalls, {id: stallId})
|
|
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this Stall link?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/shop/api/v1/stalls/' + stallId,
|
|
_.findWhere(self.g.user.wallets, {id: stall.wallet}).adminkey
|
|
)
|
|
.then(function (response) {
|
|
self.stalls = _.reject(self.stalls, function (obj) {
|
|
return obj.id == stallId
|
|
})
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
exportStallsCSV: function () {
|
|
LNbits.utils.exportCSV(this.stallsTable.columns, this.stalls)
|
|
},
|
|
////////////////////////////////////////
|
|
///////////////PRODUCTS/////////////////
|
|
////////////////////////////////////////
|
|
getProducts: function () {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/products?all_stalls=true',
|
|
self.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
if (response.data) {
|
|
self.products = response.data.map(mapProducts)
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
openProductUpdateDialog: function (linkId) {
|
|
var self = this
|
|
var link = _.findWhere(self.products, {id: linkId})
|
|
|
|
self.productDialog.data = _.clone(link._data)
|
|
self.productDialog.data.categories = self.productDialog.data.categories.split(
|
|
','
|
|
)
|
|
|
|
self.productDialog.show = true
|
|
},
|
|
sendProductFormData: function () {
|
|
let _data = {...this.productDialog.data}
|
|
var data = {
|
|
stall: _data.stall,
|
|
product: _data.product,
|
|
categories: _data.categories && _data.categories.toString(),
|
|
description: _data.description,
|
|
image: _data.image,
|
|
price: _data.price,
|
|
quantity: _data.quantity
|
|
}
|
|
if (_data.id) {
|
|
data.id = _data.id
|
|
this.updateProduct(data)
|
|
} else {
|
|
this.createProduct(data)
|
|
}
|
|
},
|
|
imageAdded(file) {
|
|
let blobURL = URL.createObjectURL(file)
|
|
let image = new Image()
|
|
image.src = blobURL
|
|
image.onload = async () => {
|
|
let fit = imgSizeFit(image)
|
|
let canvas = document.createElement('canvas')
|
|
canvas.setAttribute('width', fit.width)
|
|
canvas.setAttribute('height', fit.height)
|
|
await pica.resize(image, canvas, {
|
|
quality: 0,
|
|
alpha: true,
|
|
unsharpAmount: 95,
|
|
unsharpRadius: 0.9,
|
|
unsharpThreshold: 70
|
|
})
|
|
this.productDialog.data.image = canvas.toDataURL()
|
|
this.productDialog = {...this.productDialog}
|
|
}
|
|
},
|
|
imageCleared() {
|
|
this.productDialog.data.image = null
|
|
this.productDialog = {...this.productDialog}
|
|
},
|
|
updateProduct: function (data) {
|
|
var self = this
|
|
let wallet = _.findWhere(this.stalls, {
|
|
id: self.productDialog.data.stall
|
|
}).wallet
|
|
LNbits.api
|
|
.request(
|
|
'PUT',
|
|
'/shop/api/v1/products/' + data.id,
|
|
_.findWhere(self.g.user.wallets, {
|
|
id: wallet
|
|
}).inkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.products = _.reject(self.products, function (obj) {
|
|
return obj.id == data.id
|
|
})
|
|
self.products.push(mapProducts(response.data))
|
|
self.resetDialog('productDialog')
|
|
//self.productDialog.show = false
|
|
//self.productDialog.data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
createProduct: function (data) {
|
|
let self = this
|
|
const walletId = _.findWhere(this.stalls, {id: data.stall}).wallet
|
|
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/products',
|
|
_.findWhere(self.g.user.wallets, {id: walletId}).inkey,
|
|
data
|
|
)
|
|
.then(response => {
|
|
self.products.push(mapProducts(response.data))
|
|
self.resetDialog('productDialog')
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
deleteProduct: function (productId) {
|
|
const product = _.findWhere(this.products, {id: productId})
|
|
const walletId = _.findWhere(this.stalls, {id: product.stall}).wallet
|
|
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this products link?')
|
|
.onOk(() => {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/shop/api/v1/products/' + productId,
|
|
_.findWhere(this.g.user.wallets, {id: walletId}).adminkey
|
|
)
|
|
.then(() => {
|
|
this.products = _.reject(this.products, obj => {
|
|
return obj.id == productId
|
|
})
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
exportProductsCSV: function () {
|
|
LNbits.utils.exportCSV(this.productsTable.columns, this.products)
|
|
},
|
|
////////////////////////////////////////
|
|
//////////////////ZONE//////////////////
|
|
////////////////////////////////////////
|
|
getZones: function () {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/zones',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
if (response.data) {
|
|
self.zones = response.data.map(mapZone)
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
openZoneUpdateDialog: function (linkId) {
|
|
var self = this
|
|
var link = _.findWhere(self.zones, {id: linkId})
|
|
countriesArray = link._data.countries.split(',')
|
|
for (let i = 0; i < countriesArray.length; i++) {
|
|
countriesArray[i] = self.capitalizeFirstLetter(countriesArray[i])
|
|
}
|
|
link._data.countries = countriesArray
|
|
this.zoneDialog.data = _.clone(link._data)
|
|
this.zoneDialog.show = true
|
|
},
|
|
sendZoneFormData: function () {
|
|
var data = {
|
|
countries: String(this.zoneDialog.data.countries),
|
|
cost: parseInt(this.zoneDialog.data.cost)
|
|
}
|
|
if (this.zoneDialog.data.id) {
|
|
data.id = this.zoneDialog.data.id
|
|
this.updateZone(data)
|
|
} else {
|
|
this.createZone(data)
|
|
}
|
|
},
|
|
updateZone: function (data) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/zones/' + data.id,
|
|
self.g.user.wallets[0].adminkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.zones = _.reject(self.zones, function (obj) {
|
|
return obj.id == data.id
|
|
})
|
|
self.zones.push(mapZone(response.data))
|
|
self.zoneDialog.show = false
|
|
self.zoneDialog.data = {}
|
|
data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
createZone: function (data) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/zones',
|
|
self.g.user.wallets[0].inkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.zones.push(mapZone(response.data))
|
|
self.zoneDialog.show = false
|
|
self.zoneDialog.data = {}
|
|
data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
deleteZone: function (zoneId) {
|
|
var self = this
|
|
var zone = _.findWhere(self.zones, {id: zoneId})
|
|
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this Zone link?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/shop/api/v1/zones/' + zoneId,
|
|
self.g.user.wallets[0].adminkey
|
|
)
|
|
.then(function (response) {
|
|
self.zones = _.reject(self.zones, function (obj) {
|
|
return obj.id == zoneId
|
|
})
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
exportZonesCSV: function () {
|
|
LNbits.utils.exportCSV(this.zonesTable.columns, this.zones)
|
|
},
|
|
////////////////////////////////////////
|
|
//////////////////MARKET//////////////////
|
|
////////////////////////////////////////
|
|
getMarkets() {
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/markets',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(response => {
|
|
if (response.data) {
|
|
this.markets = response.data.map(mapMarkets)
|
|
}
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
|
|
openShopUpdateDialog: function (linkId) {
|
|
var self = this
|
|
var link = _.findWhere(self.markets, {id: linkId})
|
|
|
|
this.marketDialog.data = _.clone(link._data)
|
|
this.marketDialog.show = true
|
|
},
|
|
sendMarketplaceFormData: function () {
|
|
let data = {...this.marketDialog.data}
|
|
|
|
if (!data.usr) {
|
|
data.usr = this.g.user.id
|
|
}
|
|
|
|
if (data.id) {
|
|
this.updateZone(data)
|
|
} else {
|
|
this.createMarketplace(data)
|
|
}
|
|
},
|
|
updateShop: function (data) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'PUT',
|
|
'/shop/api/v1/shops' + data.id,
|
|
_.findWhere(self.g.user.wallets, {
|
|
id: self.marketDialog.data.wallet
|
|
}).inkey,
|
|
_.pick(data, 'countries', 'cost')
|
|
)
|
|
.then(function (response) {
|
|
self.markets = _.reject(self.markets, function (obj) {
|
|
return obj.id == data.id
|
|
})
|
|
self.markets.push(mapShops(response.data))
|
|
self.marketDialog.show = false
|
|
self.marketDialog.data = {}
|
|
data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
createMarketplace(data) {
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/markets',
|
|
this.g.user.wallets[0].inkey,
|
|
data
|
|
)
|
|
.then(response => {
|
|
this.markets.push(mapMarkets(response.data))
|
|
this.marketDialog.show = false
|
|
this.marketDialog.data = {}
|
|
data = {}
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
deleteShop: function (shopId) {
|
|
var self = this
|
|
var shop = _.findWhere(self.markets, {id: shopId})
|
|
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this Shop link?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/shop/api/v1/shops/' + shopId,
|
|
_.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey
|
|
)
|
|
.then(function (response) {
|
|
self.markets = _.reject(self.markets, function (obj) {
|
|
return obj.id == shopId
|
|
})
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
exportShopsCSV: function () {
|
|
LNbits.utils.exportCSV(this.shopsTable.columns, this.markets)
|
|
},
|
|
////////////////////////////////////////
|
|
////////////////ORDERS//////////////////
|
|
////////////////////////////////////////
|
|
getOrders: async function () {
|
|
var self = this
|
|
|
|
await LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/orders?all_wallets=true',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
if (response.data) {
|
|
self.orders = response.data.map(mapOrders)
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
/*createOrder: function () {
|
|
var data = {
|
|
address: this.orderDialog.data.address,
|
|
email: this.orderDialog.data.email,
|
|
quantity: this.orderDialog.data.quantity,
|
|
shippingzone: this.orderDialog.data.shippingzone
|
|
}
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/shop/api/v1/orders',
|
|
_.findWhere(self.g.user.wallets, {id: self.orderDialog.data.wallet})
|
|
.inkey,
|
|
data
|
|
)
|
|
.then(function (response) {
|
|
self.orders.push(mapOrders(response.data))
|
|
self.orderDialog.show = false
|
|
self.orderDialog.data = {}
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},*/
|
|
deleteOrder: function (orderId) {
|
|
var self = this
|
|
var order = _.findWhere(self.orders, {id: orderId})
|
|
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this order link?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/shop/api/v1/orders/' + orderId,
|
|
_.findWhere(self.g.user.wallets, {id: order.wallet}).adminkey
|
|
)
|
|
.then(function (response) {
|
|
self.orders = _.reject(self.orders, function (obj) {
|
|
return obj.id == orderId
|
|
})
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
},
|
|
shipOrder(order_id) {
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/shop/api/v1/orders/shipped/' + order_id,
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(response => {
|
|
this.orders = _.reject(this.orders, obj => {
|
|
return obj.id == order_id
|
|
})
|
|
this.orders.push(mapOrders(response.data))
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
exportOrdersCSV: function () {
|
|
LNbits.utils.exportCSV(this.ordersTable.columns, this.orders)
|
|
},
|
|
/// CHAT
|
|
async getAllMessages() {
|
|
await LNbits.api
|
|
.request(
|
|
'GET',
|
|
`/shop/api/v1/chat/messages/merchant?orders=${this.orders
|
|
.map(o => o.invoiceid)
|
|
.toString()}`,
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
.then(res => {
|
|
this.messages = _.groupBy(res.data, 'id_conversation')
|
|
this.checkUnreadMessages()
|
|
})
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
updateLastSeenMsg(id) {
|
|
let data = this.$q.localStorage.getItem(
|
|
`lnbits.shop.${this.g.user.id}`
|
|
)
|
|
let chat = {
|
|
...data.chat,
|
|
[`${id}`]: {
|
|
timestamp: Object.keys(this.orderMessages)[
|
|
Object.keys(this.orderMessages).length - 1
|
|
]
|
|
}
|
|
}
|
|
this.$q.localStorage.set(`lnbits.shop.${this.g.user.id}`, {
|
|
...data,
|
|
chat
|
|
})
|
|
this.checkUnreadMessages()
|
|
},
|
|
checkUnreadMessages() {
|
|
let lastMsgs = this.$q.localStorage.getItem(
|
|
`lnbits.shop.${this.g.user.id}`
|
|
).chat
|
|
for (let key in this.messages) {
|
|
let idx = this.orders.findIndex(f => f.invoiceid == key)
|
|
if (!lastMsgs[key]) {
|
|
this.updateLastSeenMsg(key)
|
|
return
|
|
}
|
|
|
|
if (
|
|
lastMsgs[key].timestamp <
|
|
Math.max(...this.messages[key].map(c => c.timestamp))
|
|
) {
|
|
this.$set(this.orders[idx], 'unread', true)
|
|
} else {
|
|
this.$set(this.orders[idx], 'unread', false)
|
|
}
|
|
}
|
|
},
|
|
clearMessage() {
|
|
this.newMessage = ''
|
|
this.$refs.newMessage.focus()
|
|
},
|
|
sendMessage() {
|
|
let message = {
|
|
msg: this.newMessage,
|
|
pubkey: this.keys.pubkey
|
|
}
|
|
this.ws.send(JSON.stringify(message))
|
|
|
|
this.clearMessage()
|
|
},
|
|
chatRoom(id) {
|
|
this.startChat(id)
|
|
this.orderMessages = {}
|
|
this.messages[id].map(m => {
|
|
this.$set(this.orderMessages, m.timestamp, {
|
|
msg: m.msg,
|
|
pubkey: m.pubkey
|
|
})
|
|
})
|
|
this.$refs.chatCard.scrollIntoView({
|
|
behavior: 'smooth',
|
|
inline: 'nearest'
|
|
})
|
|
this.updateLastSeenMsg(id)
|
|
//"ea2fbf6c91aa228603681e2cc34bb06e34e6d1375fa4d6c35756182b2fa3307f"
|
|
//"c7435a04875c26e28db91a377bd6e991dbfefeefea8258415f3ae0c716ed2335"
|
|
},
|
|
startChat(room_name) {
|
|
if (this.ws) {
|
|
this.ws.close()
|
|
}
|
|
if (location.protocol == 'https:') {
|
|
ws_scheme = 'wss://'
|
|
} else {
|
|
ws_scheme = 'ws://'
|
|
}
|
|
ws = new WebSocket(
|
|
ws_scheme + location.host + '/shop/ws/' + room_name
|
|
)
|
|
|
|
function checkWebSocket(event) {
|
|
if (ws.readyState === WebSocket.CLOSED) {
|
|
console.log('WebSocket CLOSED: Reopening')
|
|
ws = new WebSocket(
|
|
ws_scheme + location.host + '/shop/ws/' + room_name
|
|
)
|
|
}
|
|
}
|
|
|
|
ws.onmessage = event => {
|
|
let event_data = JSON.parse(event.data)
|
|
|
|
this.$set(this.orderMessages, Date.now(), event_data)
|
|
this.updateLastSeenMsg(room_name)
|
|
}
|
|
|
|
ws.onclose = event => {
|
|
this.updateLastSeenMsg(room_name)
|
|
}
|
|
|
|
this.ws = ws
|
|
}
|
|
},
|
|
async created() {
|
|
if (this.g.user.wallets.length) {
|
|
let showOnboard = this.$q.localStorage.getItem('lnbits.DAOnboarding')
|
|
this.onboarding.show = showOnboard === true || showOnboard == null
|
|
this.onboarding.showAgain = showOnboard || false
|
|
this.diagonAlley =
|
|
this.$q.localStorage.getItem('lnbits.DAmode') || false
|
|
this.getStalls()
|
|
this.getProducts()
|
|
this.getZones()
|
|
await this.getOrders()
|
|
this.getMarkets()
|
|
await this.getAllMessages()
|
|
let keys = this.$q.localStorage.getItem(
|
|
`lnbits.shop.${this.g.user.id}`
|
|
)
|
|
if (keys) {
|
|
this.keys = keys
|
|
}
|
|
setInterval(() => {
|
|
this.getAllMessages()
|
|
}, 300000)
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
<style scoped>
|
|
.q-field__native span {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.chat-container {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-rows: 1fr auto;
|
|
height: calc(100vh - 140px);
|
|
}
|
|
|
|
.chat-box {
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
padding: 1rem;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.chat-messages {
|
|
width: auto;
|
|
}
|
|
|
|
.chat-input {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: end;
|
|
margin-top: 1rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|