lnbits-legend/lnbits/extensions/shop/templates/diagonalley/index.html

2339 lines
68 KiB
HTML
Raw Normal View History

2022-01-27 12:24:38 +00:00
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
2022-07-15 10:22:13 +01:00
<!-- PRODUCT DIALOG -->
2022-01-27 12:24:38 +00:00
<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"
2022-07-12 17:22:03 +01:00
:options="stalls.map(s => ({label: s.name, value: s.id}))"
2022-01-27 12:24:38 +00:00
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>
2022-07-15 10:22:13 +01:00
<!-- <div class="row"> -->
2022-08-16 12:19:31 +01:00
<!-- <div class="col-5">
2022-01-27 12:24:38 +00:00
<q-select
filled
dense
multiple
2022-07-15 10:22:13 +01:00
v-model.trim="productDialog.data.categories"
2022-01-27 12:24:38 +00:00
:options="categories"
label="Categories"
class="q-pr-sm"
></q-select>
2022-07-15 10:22:13 +01:00
</div> -->
2022-08-16 12:19:31 +01:00
<!-- <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> -->
2022-07-15 10:22:13 +01:00
<!-- </div> -->
2022-01-27 12:24:38 +00:00
<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"
2022-07-12 17:22:03 +01:00
:disable="productDialog.data.price == null
2022-01-27 12:24:38 +00:00
|| productDialog.data.product == null
|| productDialog.data.description == null
|| productDialog.data.quantity == null"
type="submit"
>Create Product</q-btn
>
2022-08-16 12:19:31 +01:00
<q-btn
v-close-popup
flat
@click="resetDialog('productDialog')"
color="grey"
class="q-ml-auto"
2022-01-27 12:24:38 +00:00
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
2022-07-15 10:22:13 +01:00
<!-- ZONE DIALOG -->
2022-01-27 12:24:38 +00:00
<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
>
2022-08-16 12:19:31 +01:00
<q-btn
v-close-popup
flat
@click="resetDialog('zoneDialog')"
color="grey"
class="q-ml-auto"
2022-01-27 12:24:38 +00:00
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
2022-09-12 16:58:56 +01:00
<!-- MARKETPLACE/SHOP DIALOG -->
<q-dialog v-model="marketDialog.show" position="top">
2022-01-27 12:24:38 +00:00
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
2022-09-12 16:58:56 +01:00
<q-form @submit="sendMarketplaceFormData" class="q-gutter-md">
2022-01-27 12:24:38 +00:00
<q-toggle
2022-09-12 16:58:56 +01:00
label="Activate marketplace"
2022-08-16 12:19:31 +01:00
color="primary"
v-model="marketDialog.data.activate"
></q-toggle>
2022-01-27 12:24:38 +00:00
<q-select
filled
dense
multiple
2022-07-15 10:22:13 +01:00
emit-value
:options="stalls.map(s => ({label: s.name, value: s.id}))"
2022-01-27 12:24:38 +00:00
label="Stalls"
v-model.trim="marketDialog.data.stalls"
2022-01-27 12:24:38 +00:00
></q-select>
2022-09-12 16:58:56 +01:00
<q-input
filled
dense
v-model.trim="marketDialog.data.name"
label="Name"
></q-input>
2022-01-27 12:24:38 +00:00
<div class="row q-mt-lg">
<q-btn
v-if="marketDialog.data.id"
2022-01-27 12:24:38 +00:00
unelevated
color="primary"
type="submit"
2022-09-12 16:58:56 +01:00
>Update Marketplace</q-btn
2022-01-27 12:24:38 +00:00
>
<q-btn
v-else
unelevated
color="primary"
:disable="marketDialog.data.activate == null
|| marketDialog.data.stalls == null"
2022-01-27 12:24:38 +00:00
type="submit"
2022-09-12 16:58:56 +01:00
>Launch Marketplace</q-btn
2022-01-27 12:24:38 +00:00
>
2022-08-16 12:19:31 +01:00
<q-btn
v-close-popup
flat
@click="resetDialog('marketDialog')"
color="grey"
class="q-ml-auto"
2022-01-27 12:24:38 +00:00
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
2022-07-15 10:22:13 +01:00
<!-- STALL/STORE DIALOG -->
2022-01-27 12:24:38 +00:00
<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
2022-09-28 11:11:52 +01:00
v-if="keys"
2022-01-27 12:24:38 +00:00
filled
dense
v-model.trim="stallDialog.data.publickey"
label="Public Key"
></q-input>
<q-input
2022-09-28 11:11:52 +01:00
v-if="keys"
2022-01-27 12:24:38 +00:00
filled
dense
v-model.trim="stallDialog.data.privatekey"
label="Private Key"
2022-09-28 11:11:52 +01:00
></q-input>
2022-11-24 14:42:04 +00:00
<!-- 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>
2022-01-27 12:24:38 +00:00
<q-select
2022-02-06 22:40:51 +00:00
:options="zoneOptions"
2022-01-27 12:24:38 +00:00
filled
dense
multiple
v-model.trim="stallDialog.data.shippingzones"
label="Shipping Zones"
></q-select>
<!-- NOSTR -->
<!-- <q-select
2022-01-27 12:24:38 +00:00
:options="relayOptions"
filled
dense
multiple
v-model.trim="stallDialog.data.relays"
label="Relays"
></q-select>
<q-input
filled
dense
2022-02-06 22:40:51 +00:00
v-model.trim="stallDialog.data.crelays"
2022-01-27 12:24:38 +00:00
label="Custom relays (seperate by comma)"
></q-input>
<q-input
filled
dense
v-model.trim="stallDialog.data.nostrShops"
2022-02-06 22:40:51 +00:00
label="Nostr shop public keys (seperate by comma)"
></q-input> -->
2022-08-16 12:19:31 +01:00
<p>
<strong><small>Nostr support coming soon!</small></strong>
</p>
2022-01-27 12:24:38 +00:00
<div class="row q-mt-lg">
<q-btn
v-if="stallDialog.data.id"
unelevated
color="primary"
type="submit"
2022-09-23 10:25:57 +01:00
>Update Stall</q-btn
2022-01-27 12:24:38 +00:00
>
<q-btn
v-else
unelevated
color="primary"
:disable="stallDialog.data.wallet == null
2022-11-24 14:42:04 +00:00
|| stallDialog.data.shippingzones == null
|| stallDialog.data.publickey == null
|| stallDialog.data.privatekey == null"
2022-01-27 12:24:38 +00:00
type="submit"
2022-09-23 10:25:57 +01:00
>Create Stall</q-btn
2022-01-27 12:24:38 +00:00
>
2022-08-16 12:19:31 +01:00
<q-btn
v-close-popup
flat
@click="resetDialog('stallDialog')"
color="grey"
class="q-ml-auto"
2022-01-27 12:24:38 +00:00
>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
>
2022-08-16 12:19:31 +01:00
<q-btn
unelevated
v-if="zones.length > 0"
color="primary"
@click="openStallDialog()"
2022-09-23 10:25:57 +01:00
>+ Stall
<q-tooltip>
Create a market stall to list products on
</q-tooltip></q-btn
2022-02-03 22:30:53 +00:00
>
2022-08-16 12:19:31 +01:00
<q-btn
unelevated
v-else
color="primary"
@click="errorMessage('First set shipping zone(s).')"
2022-09-23 10:25:57 +01:00
>+ Stall
<q-tooltip>
Create a market stall to list products on
</q-tooltip></q-btn
2022-01-27 12:24:38 +00:00
>
<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"
2022-09-23 10:25:57 +01:00
>Create Market
2022-08-16 12:19:31 +01:00
<q-tooltip>
2022-09-23 10:25:57 +01:00
Makes a simple frontend shop for your stalls (not NOSTR)</q-tooltip
2022-08-16 12:19:31 +01:00
></q-btn
2022-01-27 12:24:38 +00:00
>
</q-card-section>
2022-12-19 15:29:02 +00:00
<q-separator inset></q-separator>
<q-card-section>
2022-12-20 08:21:48 +00:00
<div class="text-h6">Shop</div>
2022-12-19 15:29:02 +00:00
<div class="text-subtitle2">
Make a shop of multiple stalls.
2022-12-19 15:29:02 +00:00
</div>
</q-card-section>
<q-card-section>
<q-toggle
disable
v-model="diagonAlley"
checked-icon="check"
color="green"
unchecked-icon="clear"
label='"Diagon Alley" mode (Nostr)'
2022-12-19 15:29:02 +00:00
@input="toggleDA"
>
<q-tooltip>Coming soon...</q-tooltip></q-toggle
>
<q-btn
disable
class="float-right"
unelevated
color="primary"
@click="shopDataDownload"
>Export all Data
2022-12-19 15:29:02 +00:00
<q-tooltip>
Export all data (shops, products, orders, etc...)</q-tooltip
2022-12-19 15:29:02 +00:00
></q-btn
>
</q-card-section>
2022-01-27 12:24:38 +00:00
</q-card>
2022-08-16 12:19:31 +01:00
<q-card>
<!-- ORDERS TABLE -->
2022-01-27 12:24:38 +00:00
<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>
2022-01-27 12:24:38 +00:00
<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>
2022-01-27 12:24:38 +00:00
<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>
2022-01-27 12:24:38 +00:00
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
2022-08-16 12:19:31 +01:00
<q-card>
<!-- PRODUCTS TABLE -->
2022-01-27 12:24:38 +00:00
<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
2022-10-25 11:57:01 +01:00
disabled
2022-01-27 12:24:38 +00:00
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>
2022-08-16 12:19:31 +01:00
<q-card>
2022-09-23 10:25:57 +01:00
<!-- STALLS TABLE -->
2022-01-27 12:24:38 +00:00
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
2022-09-23 10:25:57 +01:00
<h5 class="text-subtitle1 q-my-none">Market Stalls</h5>
2022-01-27 12:24:38 +00:00
</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">
2022-07-19 10:13:06 +01:00
<q-th auto-width></q-th>
2022-01-27 12:24:38 +00:00
<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">
2022-07-19 10:13:06 +01:00
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="storefront"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
2022-12-20 08:21:48 +00:00
:href="'/shop/stalls/' + props.row.id"
2022-07-19 10:13:06 +01:00
target="_blank"
></q-btn>
2022-10-25 11:57:01 +01:00
<q-tooltip> Stall simple UI shopping cart </q-tooltip>
2022-07-19 10:13:06 +01:00
</q-td>
2022-01-27 12:24:38 +00:00
<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>
2022-08-16 12:19:31 +01:00
<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"
2022-12-20 08:21:48 +00:00
: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>
2022-08-16 12:19:31 +01:00
<q-card>
<!-- ZONES TABLE -->
2022-01-27 12:24:38 +00:00
<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>
2022-11-24 14:42:04 +00:00
<!-- 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 %}
2022-11-25 14:24:17 +00:00
<q-responsive
:ratio="1"
class="q-mx-xl"
@click="copyText(keys[type])"
>
2022-11-24 14:42:04 +00:00
<qrcode
:value="keys[type]"
:options="{width: 250}"
class="rounded-borders"
></qrcode>
<q-tooltip>{{ keys[type] }}</q-tooltip>
</q-responsive>
2022-11-25 14:24:17 +00:00
<p>
{{ type == 'pubkey' ? 'Public Key' : 'Private Key' }}<br /><small
>Click to copy</small
>
</p>
2022-11-24 14:42:04 +00:00
{% endraw %}
</div>
</div>
</div>
</q-card-section>
</q-card>
2022-01-27 12:24:38 +00:00
</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">
2022-12-20 08:21:48 +00:00
LNbits Shop Extension, powered by Nostr
2022-01-27 12:24:38 +00:00
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
2022-12-20 08:21:48 +00:00
<q-list> {% include "shop/_api_docs.html" %} </q-list>
2022-01-27 12:24:38 +00:00
</q-card-section>
</q-card>
<!-- CHAT BOX -->
2022-01-27 12:24:38 +00:00
<q-card>
<q-card-section>
2022-02-03 21:19:24 +00:00
<h6 class="text-subtitle1 q-my-none">Messages</h6>
2022-01-27 12:24:38 +00:00
</q-card-section>
2022-08-16 12:19:31 +01:00
<q-card-section class="q-pa-none">
2022-01-27 12:24:38 +00:00
<q-separator></q-separator>
</q-card-section>
<q-card-section>
<q-select
v-model="customerKey"
2022-11-11 12:11:14 +00:00
:options="Object.keys(messages).map(k => ({label: `${k.slice(0, 25)}...`, value: k}))"
label="Customers"
@input="chatRoom(customerKey)"
2022-11-11 12:11:14 +00:00
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">
2022-08-16 12:19:31 +01:00
<q-select
v-model="customerKey"
style="width: 80%"
:options="Object.keys(messages)"
2022-08-16 12:19:31 +01:00
label="Customers"
@input="chatRoom(customerKey)"
2022-08-16 12:19:31 +01:00
></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>
2022-02-03 21:19:24 +00:00
</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>
2022-08-16 12:19:31 +01:00
<q-chat-message v-else :text="[message[1]]"></q-chat-message>
</div>
2022-01-27 12:24:38 +00:00
</div>
2022-02-03 22:30:53 +00:00
<div class="col on-right" style="width: 90%">
2022-08-16 12:19:31 +01:00
<q-input>
2022-02-03 21:19:24 +00:00
<template v-slot:after>
<q-btn round dense flat icon="send" />
</template>
</q-input>
2022-01-27 12:24:38 +00:00
</div>
</div>
</q-card-section>
</q-card> -->
2022-01-27 12:24:38 +00:00
</div>
2022-11-27 16:50:31 +00:00
<q-dialog v-model="onboarding.show">
<q-card class="q-pa-lg">
2022-12-20 08:21:48 +00:00
<h6 class="q-my-md text-primary">How to use Shop</h6>
2022-11-27 16:50:31 +00:00
<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>
2022-02-04 13:05:48 +00:00
</div>
2022-01-27 12:24:38 +00:00
{% endblock %} {% block scripts %} {{ window_vars(user) }}
2022-07-12 17:22:03 +01:00
<script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script>
2022-09-28 11:11:52 +01:00
2022-02-04 13:05:48 +00:00
<script>
2022-08-16 12:19:31 +01:00
Vue.component(VueQrcode.name, VueQrcode)
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
const pica = window.pica()
2022-02-04 13:05:48 +00:00
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}
}
2022-08-16 12:19:31 +01:00
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
2022-08-16 12:19:31 +01:00
return obj
}
const mapKeys = obj => {
obj._data = _.clone(obj)
return obj
}
2022-02-04 13:05:48 +00:00
2022-09-12 16:58:56 +01:00
const mapMarkets = obj => {
obj._data = _.clone(obj)
obj.stores = []
LNbits.api
2022-12-20 08:21:48 +00:00
.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)
})
2022-09-12 16:58:56 +01:00
return obj
}
2022-08-16 12:19:31 +01:00
const humanReadableZones = zones => {
return zones.map(z => `${z.id} - ${z.countries}`)
}
2022-07-12 17:22:03 +01:00
2022-08-16 12:19:31 +01:00
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
2022-11-27 16:50:31 +00:00
step: 1,
onboarding: {
show: true,
showAgain: false,
finish: () => {
this.$q.localStorage.set(
'lnbits.DAOnboarding',
this.onboarding.showAgain
)
this.onboarding.show = false
}
},
2022-09-28 11:11:52 +01:00
keys: null,
2022-12-19 15:29:02 +00:00
diagonAlley: false,
2022-08-16 12:19:31 +01:00
products: [],
orders: [],
stalls: [],
markets: [],
2022-08-16 12:19:31 +01:00
zones: [],
zoneOptions: [],
customerKeys: [],
customerKey: '',
customerMessages: {},
messages: {},
newMessage: '',
orderMessages: {},
2022-08-16 12:19:31 +01:00
shippedModel: false,
shippingZoneOptions: [
'Free (digital)',
2022-08-16 12:19:31 +01:00
'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'
2022-02-04 13:05:48 +00:00
],
2022-08-16 12:19:31 +01:00
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'
2022-02-04 13:05:48 +00:00
],
2022-08-16 12:19:31 +01:00
relayOptions: [
'wss://nostr-relay.herokuapp.com/ws',
'wss://nostr-relay.bigsun.xyz/ws',
'wss://freedom-relay.herokuapp.com/ws'
2022-02-04 13:05:48 +00:00
],
2022-08-16 12:19:31 +01:00
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'
2022-08-16 12:19:31 +01:00
},
{
name: 'time',
2022-08-16 12:19:31 +01:00
align: 'left',
label: 'Date',
field: 'time'
2022-08-16 12:19:31 +01:00
},
{
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',
2022-09-23 10:25:57 +01:00
label: 'Stall',
2022-08-16 12:19:31 +01:00
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',
2022-09-23 10:25:57 +01:00
label: 'Stalls',
field: 'stores'
}
],
pagination: {
rowsPerPage: 10
}
},
2022-08-16 12:19:31 +01:00
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: {}
2022-02-04 13:05:48 +00:00
}
2022-08-16 12:19:31 +01:00
}
},
computed: {
categoryOther: function () {
cats = trim(this.productDialog.data.categories.split(','))
for (let i = 0; i < cats.length; i++) {
if (cats[i] == 'Others') {
return true
2022-02-04 13:05:48 +00:00
}
}
2022-08-16 12:19:31 +01:00
return false
}
},
methods: {
resetDialog(dialog) {
this[dialog].show = false
this[dialog].data = {}
2022-02-04 13:05:48 +00:00
},
2022-12-19 15:29:02 +00:00
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)
},
2022-09-28 11:11:52 +01:00
generateKeys() {
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/keys',
2022-09-28 11:11:52 +01:00
this.g.user.wallets[0].adminkey
2022-08-16 12:19:31 +01:00
)
2022-09-28 11:11:52 +01:00
.then(response => {
2022-08-16 12:19:31 +01:00
if (response.data) {
2022-09-28 11:11:52 +01:00
this.keys = response.data
this.stallDialog.data.publickey = this.keys.pubkey
this.stallDialog.data.privatekey = this.keys.privkey
this.$q.localStorage.set(
2022-12-20 08:21:48 +00:00
`lnbits.shop.${this.g.user.id}`,
2022-09-28 11:11:52 +01:00
this.keys
)
2022-08-16 12:19:31 +01:00
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
2022-02-04 13:05:48 +00:00
},
2022-09-28 11:11:52 +01:00
restoreKeys() {
let keys = this.$q.localStorage.getItem(
2022-12-20 08:21:48 +00:00
`lnbits.shop.${this.g.user.id}`
2022-09-28 11:11:52 +01:00
)
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.'
})
}
},
2022-11-24 14:42:04 +00:00
exportKeysCSV: function () {
let colls = [
2022-11-25 14:24:17 +00:00
{
name: 'privatekey',
align: 'left',
label: 'Private Key',
field: 'privkey'
},
{
name: 'publickey',
align: 'left',
label: 'Public Key',
field: 'pubkey'
}
2022-11-24 14:42:04 +00:00
]
LNbits.utils.exportCSV(colls, [this.keys])
},
2022-08-16 12:19:31 +01:00
capitalizeFirstLetter: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
2022-02-04 13:05:48 +00:00
},
2022-08-16 12:19:31 +01:00
errorMessage: function (error) {
this.$q.notify({
color: 'primary',
message: error
})
2022-02-04 13:05:48 +00:00
},
2022-08-16 12:19:31 +01:00
////////////////////////////////////////
///////////SUPPORT MESSAGES/////////////
////////////////////////////////////////
getMessages: function (customerKey) {
var self = this
messages = []
messages.push(['in', 'blah blah'])
messages.push(['out', 'blah blah'])
self.customerMessages = messages
2022-02-04 13:05:48 +00:00
},
2022-08-16 12:19:31 +01:00
////////////////////////////////////////
////////////////STALLS//////////////////
////////////////////////////////////////
getStalls: function () {
var self = this
LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/stalls?all_wallets=true',
2022-08-16 12:19:31 +01:00
self.g.user.wallets[0].adminkey
)
.then(function (response) {
if (response.data) {
self.stalls = response.data.map(mapStalls)
}
})
.catch(function (error) {
2022-02-12 12:40:12 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
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())
2022-02-04 13:05:48 +00:00
)
2022-08-16 12:19:31 +01:00
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',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/stalls/' + data.id,
2022-08-16 12:19:31 +01:00
_.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) {
2022-02-04 13:05:48 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
createStall: function (data) {
var self = this
LNbits.api
.request(
'POST',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/stalls',
2022-08-16 12:19:31 +01:00
_.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 = {}
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteStall: function (stallId) {
var self = this
var stall = _.findWhere(self.stalls, {id: stallId})
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.utils
.confirmDialog('Are you sure you want to delete this Stall link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/stalls/' + stallId,
2022-08-16 12:19:31 +01:00
_.findWhere(self.g.user.wallets, {id: stall.wallet}).adminkey
)
.then(function (response) {
self.stalls = _.reject(self.stalls, function (obj) {
return obj.id == stallId
})
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportStallsCSV: function () {
LNbits.utils.exportCSV(this.stallsTable.columns, this.stalls)
},
////////////////////////////////////////
///////////////PRODUCTS/////////////////
////////////////////////////////////////
getProducts: function () {
var self = this
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/products?all_stalls=true',
2022-08-16 12:19:31 +01:00
self.g.user.wallets[0].inkey
)
.then(function (response) {
if (response.data) {
self.products = response.data.map(mapProducts)
}
})
.catch(function (error) {
2022-02-04 13:05:48 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
openProductUpdateDialog: function (linkId) {
var self = this
var link = _.findWhere(self.products, {id: linkId})
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
self.productDialog.data = _.clone(link._data)
self.productDialog.data.categories = self.productDialog.data.categories.split(
','
2022-02-04 13:05:48 +00:00
)
2022-08-16 12:19:31 +01:00
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)
2022-08-16 12:19:31 +01:00
let canvas = document.createElement('canvas')
canvas.setAttribute('width', fit.width)
canvas.setAttribute('height', fit.height)
2022-08-16 12:19:31 +01:00
await pica.resize(image, canvas, {
quality: 0,
alpha: true,
unsharpAmount: 95,
unsharpRadius: 0.9,
unsharpThreshold: 70
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
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',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/products/' + data.id,
2022-08-16 12:19:31 +01:00
_.findWhere(self.g.user.wallets, {
id: wallet
}).inkey,
data
)
.then(function (response) {
self.products = _.reject(self.products, function (obj) {
return obj.id == data.id
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
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
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'POST',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/products',
2022-08-16 12:19:31 +01:00
_.findWhere(self.g.user.wallets, {id: walletId}).inkey,
data
)
.then(response => {
self.products.push(mapProducts(response.data))
self.resetDialog('productDialog')
})
.catch(error => {
2022-02-04 13:05:48 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
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',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/products/' + productId,
2022-08-16 12:19:31 +01:00
_.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',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/zones',
2022-08-16 12:19:31 +01:00
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 = {
2022-02-04 13:05:48 +00:00
countries: String(this.zoneDialog.data.countries),
cost: parseInt(this.zoneDialog.data.cost)
2022-08-16 12:19:31 +01:00
}
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',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/zones/' + data.id,
2022-08-16 12:19:31 +01:00
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 = {}
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
createZone: function (data) {
var self = this
LNbits.api
.request(
'POST',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/zones',
2022-08-16 12:19:31 +01:00
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})
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.utils
.confirmDialog('Are you sure you want to delete this Zone link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/zones/' + zoneId,
2022-08-16 12:19:31 +01:00
self.g.user.wallets[0].adminkey
)
.then(function (response) {
self.zones = _.reject(self.zones, function (obj) {
return obj.id == zoneId
})
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportZonesCSV: function () {
LNbits.utils.exportCSV(this.zonesTable.columns, this.zones)
},
////////////////////////////////////////
//////////////////MARKET//////////////////
////////////////////////////////////////
getMarkets() {
LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/markets',
2022-08-16 12:19:31 +01:00
this.g.user.wallets[0].inkey
)
.then(response => {
if (response.data) {
2022-09-12 16:58:56 +01:00
this.markets = response.data.map(mapMarkets)
2022-08-16 12:19:31 +01:00
}
})
.catch(error => {
2022-02-04 13:05:48 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
2022-08-16 12:19:31 +01:00
openShopUpdateDialog: function (linkId) {
var self = this
2022-09-12 16:58:56 +01:00
var link = _.findWhere(self.markets, {id: linkId})
2022-02-04 13:05:48 +00:00
2022-09-12 16:58:56 +01:00
this.marketDialog.data = _.clone(link._data)
this.marketDialog.show = true
2022-08-16 12:19:31 +01:00
},
2022-09-12 16:58:56 +01:00
sendMarketplaceFormData: function () {
2022-08-16 12:19:31 +01:00
let data = {...this.marketDialog.data}
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
if (!data.usr) {
data.usr = this.g.user.id
}
if (data.id) {
this.updateZone(data)
} else {
2022-09-12 16:58:56 +01:00
this.createMarketplace(data)
2022-08-16 12:19:31 +01:00
}
},
updateShop: function (data) {
var self = this
LNbits.api
.request(
'PUT',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/shops' + data.id,
2022-08-16 12:19:31 +01:00
_.findWhere(self.g.user.wallets, {
2022-09-12 16:58:56 +01:00
id: self.marketDialog.data.wallet
2022-08-16 12:19:31 +01:00
}).inkey,
_.pick(data, 'countries', 'cost')
)
.then(function (response) {
2022-09-12 16:58:56 +01:00
self.markets = _.reject(self.markets, function (obj) {
2022-08-16 12:19:31 +01:00
return obj.id == data.id
})
2022-09-12 16:58:56 +01:00
self.markets.push(mapShops(response.data))
self.marketDialog.show = false
self.marketDialog.data = {}
2022-08-16 12:19:31 +01:00
data = {}
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
2022-09-12 16:58:56 +01:00
createMarketplace(data) {
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'POST',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/markets',
2022-08-16 12:19:31 +01:00
this.g.user.wallets[0].inkey,
data
)
.then(response => {
2022-09-12 16:58:56 +01:00
this.markets.push(mapMarkets(response.data))
this.marketDialog.show = false
this.marketDialog.data = {}
2022-08-16 12:19:31 +01:00
data = {}
})
.catch(error => {
LNbits.utils.notifyApiError(error)
})
},
deleteShop: function (shopId) {
var self = this
2022-09-12 16:58:56 +01:00
var shop = _.findWhere(self.markets, {id: shopId})
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.utils
.confirmDialog('Are you sure you want to delete this Shop link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/shops/' + shopId,
2022-08-16 12:19:31 +01:00
_.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey
)
.then(function (response) {
2022-09-12 16:58:56 +01:00
self.markets = _.reject(self.markets, function (obj) {
2022-08-16 12:19:31 +01:00
return obj.id == shopId
})
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportShopsCSV: function () {
2022-09-12 16:58:56 +01:00
LNbits.utils.exportCSV(this.shopsTable.columns, this.markets)
2022-08-16 12:19:31 +01:00
},
////////////////////////////////////////
////////////////ORDERS//////////////////
////////////////////////////////////////
getOrders: async function () {
2022-08-16 12:19:31 +01:00
var self = this
2022-02-04 13:05:48 +00:00
await LNbits.api
2022-08-16 12:19:31 +01:00
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/orders?all_wallets=true',
2022-08-16 12:19:31 +01:00
this.g.user.wallets[0].inkey
)
.then(function (response) {
if (response.data) {
self.orders = response.data.map(mapOrders)
}
})
.catch(function (error) {
2022-02-04 13:05:48 +00:00
LNbits.utils.notifyApiError(error)
})
2022-08-16 12:19:31 +01:00
},
2022-11-11 12:11:14 +00:00
/*createOrder: function () {
2022-08-16 12:19:31 +01:00
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
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'POST',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/orders',
2022-08-16 12:19:31 +01:00
_.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)
})
2022-11-11 12:11:14 +00:00
},*/
2022-08-16 12:19:31 +01:00
deleteOrder: function (orderId) {
var self = this
var order = _.findWhere(self.orders, {id: orderId})
2022-02-04 13:05:48 +00:00
2022-08-16 12:19:31 +01:00
LNbits.utils
.confirmDialog('Are you sure you want to delete this order link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/orders/' + orderId,
2022-11-11 12:11:14 +00:00
_.findWhere(self.g.user.wallets, {id: order.wallet}).adminkey
2022-08-16 12:19:31 +01:00
)
.then(function (response) {
self.orders = _.reject(self.orders, function (obj) {
return obj.id == orderId
})
2022-02-04 13:05:48 +00:00
})
2022-08-16 12:19:31 +01:00
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
2022-10-25 12:32:19 +01:00
shipOrder(order_id) {
2022-08-16 12:19:31 +01:00
LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
'/shop/api/v1/orders/shipped/' + order_id,
2022-08-16 12:19:31 +01:00
this.g.user.wallets[0].inkey
)
2022-10-25 12:32:19 +01:00
.then(response => {
this.orders = _.reject(this.orders, obj => {
2022-10-25 11:57:01 +01:00
return obj.id == order_id
})
this.orders.push(mapOrders(response.data))
})
2022-10-25 12:32:19 +01:00
.catch(error => {
2022-10-25 11:57:01 +01:00
LNbits.utils.notifyApiError(error)
2022-08-16 12:19:31 +01:00
})
},
exportOrdersCSV: function () {
LNbits.utils.exportCSV(this.ordersTable.columns, this.orders)
},
/// CHAT
async getAllMessages() {
await LNbits.api
.request(
'GET',
2022-12-20 08:21:48 +00:00
`/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(
2022-12-20 08:21:48 +00:00
`lnbits.shop.${this.g.user.id}`
)
let chat = {
...data.chat,
[`${id}`]: {
timestamp: Object.keys(this.orderMessages)[
Object.keys(this.orderMessages).length - 1
]
}
}
2022-12-20 08:21:48 +00:00
this.$q.localStorage.set(`lnbits.shop.${this.g.user.id}`, {
...data,
chat
})
this.checkUnreadMessages()
},
checkUnreadMessages() {
let lastMsgs = this.$q.localStorage.getItem(
2022-12-20 08:21:48 +00:00
`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
}
2022-10-25 12:32:19 +01:00
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(
2022-12-20 08:21:48 +00:00
ws_scheme + location.host + '/shop/ws/' + room_name
)
function checkWebSocket(event) {
if (ws.readyState === WebSocket.CLOSED) {
console.log('WebSocket CLOSED: Reopening')
ws = new WebSocket(
2022-12-20 08:21:48 +00:00
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
2022-08-16 12:19:31 +01:00
}
2022-02-04 13:05:48 +00:00
},
async created() {
2022-08-16 12:19:31 +01:00
if (this.g.user.wallets.length) {
2022-11-27 16:50:31 +00:00
let showOnboard = this.$q.localStorage.getItem('lnbits.DAOnboarding')
this.onboarding.show = showOnboard === true || showOnboard == null
this.onboarding.showAgain = showOnboard || false
2022-12-19 15:29:02 +00:00
this.diagonAlley =
this.$q.localStorage.getItem('lnbits.DAmode') || false
2022-08-16 12:19:31 +01:00
this.getStalls()
this.getProducts()
this.getZones()
await this.getOrders()
2022-09-15 15:51:12 +01:00
this.getMarkets()
await this.getAllMessages()
let keys = this.$q.localStorage.getItem(
2022-12-20 08:21:48 +00:00
`lnbits.shop.${this.g.user.id}`
)
if (keys) {
this.keys = keys
}
setInterval(() => {
this.getAllMessages()
}, 300000)
2022-08-16 12:19:31 +01:00
}
2022-02-04 13:05:48 +00:00
}
2022-08-16 12:19:31 +01:00
})
2022-02-04 13:05:48 +00:00
</script>
<style scoped>
2022-11-11 12:12:31 +00:00
.q-field__native span {
2022-11-11 12:11:14 +00:00
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>
2022-01-27 12:24:38 +00:00
{% endblock %}