Merge pull request #161 from lnbits/ui_fixup

fixup: ui
This commit is contained in:
Arc 2025-12-27 21:56:57 +00:00 committed by GitHub
commit 2bde92bef6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 357 additions and 387 deletions

View file

@ -74,6 +74,9 @@ window.app.component('stall-list', {
} }
}, },
methods: { methods: {
emitStallCount: function () {
this.$emit('stalls-updated', this.stalls.length)
},
sendStallFormData: async function () { sendStallFormData: async function () {
const stallData = { const stallData = {
name: this.stallDialog.data.name, name: this.stallDialog.data.name,
@ -101,6 +104,7 @@ window.app.component('stall-list', {
) )
this.stallDialog.show = false this.stallDialog.show = false
this.stalls.unshift(data) this.stalls.unshift(data)
this.emitStallCount()
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Stall created!' message: 'Stall created!'
@ -120,6 +124,7 @@ window.app.component('stall-list', {
) )
this.stallDialog.show = false this.stallDialog.show = false
this.stalls.unshift(data) this.stalls.unshift(data)
this.emitStallCount()
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Stall restored!' message: 'Stall restored!'
@ -151,6 +156,7 @@ window.app.component('stall-list', {
if (index !== -1) { if (index !== -1) {
this.stalls.splice(index, 1, data) this.stalls.splice(index, 1, data)
} }
this.emitStallCount()
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Stall updated!' message: 'Stall updated!'
@ -168,6 +174,7 @@ window.app.component('stall-list', {
) )
this.stalls = this.stalls.filter(s => s.id !== stall.id) this.stalls = this.stalls.filter(s => s.id !== stall.id)
this.pendingStalls = this.pendingStalls.filter(s => s.id !== stall.id) this.pendingStalls = this.pendingStalls.filter(s => s.id !== stall.id)
this.emitStallCount()
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Stall deleted' message: 'Stall deleted'
@ -291,6 +298,7 @@ window.app.component('stall-list', {
}, },
created: async function () { created: async function () {
this.stalls = await this.getStalls() this.stalls = await this.getStalls()
this.emitStallCount()
this.currencies = this.getCurrencies() this.currencies = this.getCurrencies()
this.zoneOptions = await this.getZones() this.zoneOptions = await this.getZones()
} }

View file

@ -12,6 +12,7 @@ window.app = Vue.createApp({
activeChatCustomer: '', activeChatCustomer: '',
orderPubkey: null, orderPubkey: null,
showKeys: false, showKeys: false,
stallCount: 0,
importKeyDialog: { importKeyDialog: {
show: false, show: false,
data: { data: {
@ -114,6 +115,7 @@ window.app = Vue.createApp({
this.shippingZones = [] this.shippingZones = []
this.activeChatCustomer = '' this.activeChatCustomer = ''
this.showKeys = false this.showKeys = false
this.stallCount = 0
}, },
createMerchant: async function (privateKey) { createMerchant: async function (privateKey) {
try { try {
@ -378,7 +380,6 @@ window.app = Vue.createApp({
}, },
goToOrders: function (stallId) { goToOrders: function (stallId) {
this.selectedStallFilter = stallId this.selectedStallFilter = stallId
this.activeTab = 'orders'
} }
}, },
created: async function () { created: async function () {

View file

@ -1,143 +1,146 @@
<div> <div>
<q-card> <q-card>
<q-card-section> <q-expansion-item
<div class="row items-center q-col-gutter-sm"> icon="chat"
<div class="col-auto"> label="Messages"
<h6 class="text-subtitle1 q-my-none">Messages</h6> header-class="text-grey"
</div> expand-separator
<div class="col-auto"> >
<q-badge v-if="unreadMessages" color="primary" outline <q-card-section class="q-pb-none">
><span v-text="unreadMessages"></span>&nbsp; new</q-badge <div class="row items-center q-col-gutter-sm">
> <div class="col-auto">
</div> <q-badge v-if="unreadMessages" color="primary" outline
<div class="col-auto q-ml-auto"> ><span v-text="unreadMessages"></span>&nbsp; new</q-badge
<q-btn >
v-if="activePublicKey" </div>
@click="showClientOrders" <div class="col-auto q-ml-auto">
unelevated <q-btn
outline v-if="activePublicKey"
size="sm" @click="showClientOrders"
>Client Orders</q-btn unelevated
> outline
</div> size="sm"
</div> >Client Orders</q-btn
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
</q-card-section>
<q-card-section>
<div class="row q-col-gutter-sm items-end">
<div class="col" style="min-width: 0">
<q-select
v-model="activePublicKey"
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
label="Select Customer"
emit-value
@input="selectActiveCustomer()"
:display-value="activePublicKey ? buildCustomerLabel(customers.find(c => c.public_key === activePublicKey)) : ''"
class="ellipsis"
>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section>
<q-item-label>
<span v-text="scope.opt.label.split('(')[0]"></span>
</q-item-label>
<q-item-label
caption
class="text-mono"
style="word-break: break-all"
>
<span v-text="scope.opt.value"></span>
</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-auto">
<q-btn
label="ADD"
color="primary"
unelevated
@click="showAddPublicKey = true"
>
<q-tooltip> Add a public key to chat with </q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="chat-container" ref="chatCard">
<div class="chat-box">
<div class="chat-messages" style="height: 45vh">
<q-chat-message
v-for="(dm, index) in messagesAsJson"
:key="index"
:name="dm.incoming ? 'customer': 'me'"
:sent="!dm.incoming"
:stamp="dm.dateFrom"
:bg-color="dm.incoming ? 'white' : 'light-green-2'"
:class="'chat-mesage-index-'+index"
> >
<div v-if="dm.isJson">
<div v-if="dm.message.type === 0">
<strong>New order:</strong>
</div>
<div v-else-if="dm.message.type === 1">
<strong>Reply sent for order: </strong>
</div>
<div v-else-if="dm.message.type === 2">
<q-badge v-if="dm.message.paid" color="green">Paid </q-badge>
<q-badge v-if="dm.message.shipped" color="green"
>Shipped
</q-badge>
</div>
<div>
<span v-text="dm.message.message"></span>
<q-badge color="orange">
<span
v-text="dm.message.id"
@click="showOrderDetails(dm.message.id, dm.event_id)"
class="cursor-pointer"
></span>
</q-badge>
</div>
<q-badge
@click="showMessageRawData(index)"
class="cursor-pointer"
>...</q-badge
>
</div>
<div v-else><span v-text="dm.message"></span></div>
</q-chat-message>
</div> </div>
</div> </div>
<q-card-section> </q-card-section>
<q-form @submit="sendDirectMesage" class="full-width chat-input"> <q-card-section>
<q-input <div class="row q-col-gutter-sm items-end">
ref="newMessage" <div class="col" style="min-width: 0">
v-model="newMessage" <q-select
placeholder="Message" v-model="activePublicKey"
class="full-width" :options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
dense label="Select Customer"
outlined emit-value
@input="selectActiveCustomer()"
:display-value="activePublicKey ? buildCustomerLabel(customers.find(c => c.public_key === activePublicKey)) : ''"
class="ellipsis"
> >
<template> <template v-slot:option="scope">
<q-btn <q-item v-bind="scope.itemProps">
round <q-item-section>
dense <q-item-label>
flat <span v-text="scope.opt.label.split('(')[0]"></span>
type="submit" </q-item-label>
icon="send" <q-item-label
color="primary" caption
/> class="text-mono"
style="word-break: break-all"
>
<span v-text="scope.opt.value"></span>
</q-item-label>
</q-item-section>
</q-item>
</template> </template>
</q-input> </q-select>
</q-form> </div>
</q-card-section> <div class="col-auto">
</div> <q-btn
</q-card-section> label="ADD"
color="primary"
unelevated
@click="showAddPublicKey = true"
>
<q-tooltip> Add a public key to chat with </q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="chat-container" ref="chatCard">
<div class="chat-box">
<div class="chat-messages" style="height: 45vh">
<q-chat-message
v-for="(dm, index) in messagesAsJson"
:key="index"
:name="dm.incoming ? 'customer': 'me'"
:sent="!dm.incoming"
:stamp="dm.dateFrom"
:bg-color="dm.incoming ? 'white' : 'light-green-2'"
:class="'chat-mesage-index-'+index"
>
<div v-if="dm.isJson">
<div v-if="dm.message.type === 0">
<strong>New order:</strong>
</div>
<div v-else-if="dm.message.type === 1">
<strong>Reply sent for order: </strong>
</div>
<div v-else-if="dm.message.type === 2">
<q-badge v-if="dm.message.paid" color="green"
>Paid
</q-badge>
<q-badge v-if="dm.message.shipped" color="green"
>Shipped
</q-badge>
</div>
<div>
<span v-text="dm.message.message"></span>
<q-badge color="orange">
<span
v-text="dm.message.id"
@click="showOrderDetails(dm.message.id, dm.event_id)"
class="cursor-pointer"
></span>
</q-badge>
</div>
<q-badge
@click="showMessageRawData(index)"
class="cursor-pointer"
>...</q-badge
>
</div>
<div v-else><span v-text="dm.message"></span></div>
</q-chat-message>
</div>
</div>
<q-card-section>
<q-form @submit="sendDirectMesage" 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>
</q-card-section>
</div>
</q-card-section>
</q-expansion-item>
</q-card> </q-card>
<div> <div>
<q-dialog v-model="showAddPublicKey" position="top"> <q-dialog v-model="showAddPublicKey" position="top">

View file

@ -28,19 +28,6 @@
></span> ></span>
</div> </div>
</div> </div>
<div v-if="isAdmin" class="col-12 col-sm-auto q-ml-sm-auto">
<q-btn
label="Restart Nostr Connection"
color="grey"
outline
size="sm"
@click="restartNostrConnection"
>
<q-tooltip>
Restart the connection to the nostrclient extension
</q-tooltip>
</q-btn>
</div>
</div> </div>
<div v-if="showKeys" class="q-mt-md"> <div v-if="showKeys" class="q-mt-md">
<div class="row q-mb-md"> <div class="row q-mb-md">
@ -65,40 +52,5 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-md-4">
<q-card flat bordered>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">Nostr Market Extension</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
<q-expansion-item group="api" dense icon="info" label="About">
<q-card>
<q-card-section>
A decentralized marketplace powered by Nostr and Lightning
Network. Create stalls, add products, and start selling with
Bitcoin.
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
icon="link"
label="Market Client"
>
<q-card>
<q-card-section>
<a :href="marketClientUrl" target="_blank"
>Open Market Client</a
>
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
</q-card-section>
</q-card>
</div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context {% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %} %} {% block page %}
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div class="col-12"> <div class="col-12 col-md-7 col-lg-8 q-gutter-y-md">
<div v-if="merchant && merchant.id"> <div v-if="merchant && merchant.id">
<q-card> <q-card>
<div class="row items-center no-wrap"> <div class="row items-center no-wrap">
@ -36,106 +36,7 @@
icon="inventory_2" icon="inventory_2"
style="min-width: 120px" style="min-width: 120px"
></q-tab> ></q-tab>
<q-tab
name="messages"
label="Messages"
icon="chat"
style="min-width: 120px"
></q-tab>
<q-tab
name="orders"
label="Orders"
icon="receipt"
style="min-width: 120px"
></q-tab>
</q-tabs> </q-tabs>
<div class="col-auto q-mr-md">
<q-btn-dropdown
color="primary"
label="Publish"
icon="publish"
unelevated
>
<q-list>
<q-item clickable v-close-popup @click="publishNip15">
<q-item-section avatar>
<q-icon name="store" />
</q-item-section>
<q-item-section>
<q-item-label>Publish NIP-15</q-item-label>
<q-item-label caption
>Publish stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="sell" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Publish NIP-99</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="refreshNip15">
<q-item-section avatar>
<q-icon name="refresh" />
</q-item-section>
<q-item-section>
<q-item-label>Refresh NIP-15 from Nostr</q-item-label>
<q-item-label caption
>Sync stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="refresh" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Refresh NIP-99 from Nostr</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="deleteNip15">
<q-item-section avatar>
<q-icon name="delete_forever" color="negative" />
</q-item-section>
<q-item-section>
<q-item-label class="text-negative"
>Delete NIP-15 from Nostr</q-item-label
>
<q-item-label caption
>Remove stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="delete_forever" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Delete NIP-99 from Nostr</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div> </div>
<q-separator></q-separator> <q-separator></q-separator>
@ -177,6 +78,7 @@
@customer-selected-for-order="customerSelectedForOrder" @customer-selected-for-order="customerSelectedForOrder"
@go-to-products="goToProducts" @go-to-products="goToProducts"
@go-to-orders="goToOrders" @go-to-orders="goToOrders"
@stalls-updated="stallCount = $event"
></stall-list> ></stall-list>
</q-tab-panel> </q-tab-panel>
@ -191,31 +93,23 @@
</q-tab-panel> </q-tab-panel>
<!-- Messages Tab --> <!-- Messages Tab -->
<q-tab-panel name="messages">
<direct-messages
ref="directMessagesRef"
:inkey="g.user.wallets[0].inkey"
:adminkey="g.user.wallets[0].adminkey"
:active-chat-customer="activeChatCustomer"
:merchant-id="merchant.id"
@customer-selected="filterOrdersForCustomer"
@order-selected="showOrderDetails"
>
</direct-messages>
</q-tab-panel>
<!-- Orders Tab -->
<q-tab-panel name="orders">
<order-list
ref="orderListRef"
:adminkey="g.user.wallets[0].adminkey"
:inkey="g.user.wallets[0].inkey"
:customer-pubkey-filter="orderPubkey"
@customer-selected="customerSelectedForOrder"
></order-list>
</q-tab-panel>
</q-tab-panels> </q-tab-panels>
</q-card> </q-card>
<q-card class="q-mt-md">
<q-card-section>
<div class="text-h6">Orders</div>
</q-card-section>
<q-separator></q-separator>
<q-card-section class="q-pt-none">
<order-list
ref="orderListRef"
:adminkey="g.user.wallets[0].adminkey"
:inkey="g.user.wallets[0].inkey"
:customer-pubkey-filter="orderPubkey"
@customer-selected="customerSelectedForOrder"
></order-list>
</q-card-section>
</q-card>
</div> </div>
<q-card v-else> <q-card v-else>
<q-card-section> <q-card-section>
@ -274,94 +168,178 @@
</q-card> </q-card>
</div> </div>
<div class="col-12 col-md-5 q-gutter-y-md"> <div class="col-12 col-md-5 col-lg-4 q-gutter-y-md">
<div v-if="g.user.admin" class="col-12 q-mb-lg"> <div v-if="g.user.admin" class="col-12 q-mb-lg">
<q-card> <q-card>
<q-card-section class="q-pa-md"> <q-card-section class="q-pa-md">
<q-btn-dropdown <div class="row items-center no-wrap q-col-gutter-sm">
:color="nostrStatusColor" <div class="col">
:label="nostrStatusLabel" <q-btn-dropdown
icon="sync" :color="nostrStatusColor"
split :label="nostrStatusLabel"
@click="restartNostrConnection" icon="sync"
> split
<q-list> @click="restartNostrConnection"
<q-item clickable v-close-popup @click="restartNostrConnection"> >
<q-item-section avatar> <q-list>
<q-icon name="refresh" color="primary"></q-icon> <q-item
</q-item-section> clickable
<q-item-section> v-close-popup
<q-item-label>Restart Connection</q-item-label> @click="restartNostrConnection"
<q-item-label caption>
Reconnect to the nostrclient extension
</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="checkNostrStatus(true)">
<q-item-section avatar>
<q-icon name="wifi_find" color="primary"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>Check Status</q-item-label>
<q-item-label caption>
Check connection to nostrclient
</q-item-label>
</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item>
<q-item-section>
<q-item-label caption>
<strong>Status:</strong>
<q-badge
:color="nostrStatus.connected ? 'green' : 'red'"
class="q-ml-xs"
v-text="nostrStatus.connected ? 'Connected' : 'Disconnected'"
></q-badge>
</q-item-label>
<q-item-label
v-if="nostrStatus.relays_total > 0"
caption
class="q-mt-xs"
> >
<strong>Relays:</strong>&nbsp; <q-item-section avatar>
<span v-text="nostrStatus.relays_connected"></span> <q-icon name="refresh" color="primary"></q-icon>
of </q-item-section>
<span v-text="nostrStatus.relays_total"></span> <q-item-section>
connected <q-item-label>Restart Connection</q-item-label>
</q-item-label> <q-item-label caption>
<q-item-label Reconnect to the nostrclient extension
v-if="nostrStatus.error" </q-item-label>
caption </q-item-section>
class="text-negative q-mt-xs" </q-item>
v-text="nostrStatus.error" <q-item
></q-item-label> clickable
</q-item-section> v-close-popup
</q-item> @click="checkNostrStatus(true)"
</q-list> >
</q-btn-dropdown> <q-item-section avatar>
</q-card-section> <q-icon name="wifi_find" color="primary"></q-icon>
</q-card> </q-item-section>
</div> <q-item-section>
<div class="col-12"> <q-item-label>Check Status</q-item-label>
<q-card> <q-item-label caption>
<q-img Check connection to nostrclient
src="/nostrmarket/static/market/images/nostr-cover.png" </q-item-label>
:ratio="3" </q-item-section>
fit="cover" </q-item>
></q-img> <q-separator></q-separator>
<q-card-section> <q-item>
<div class="text-h6 q-mb-sm">Nostr Market</div> <q-item-section>
<div class="text-body2 text-grey"> <q-item-label caption>
A decentralized marketplace extension for LNbits implementing the <strong>Status:</strong>
NIP-15 protocol. Create stalls, list products, and accept Lightning <q-badge
payments while communicating with customers via encrypted Nostr :color="nostrStatus.connected ? 'green' : 'red'"
direct messages. class="q-ml-xs"
v-text="nostrStatus.connected ? 'Connected' : 'Disconnected'"
></q-badge>
</q-item-label>
<q-item-label
v-if="nostrStatus.relays_total > 0"
caption
class="q-mt-xs"
>
<strong>Relays:</strong>&nbsp;
<span v-text="nostrStatus.relays_connected"></span>
of
<span v-text="nostrStatus.relays_total"></span>
connected
</q-item-label>
<q-item-label
v-if="nostrStatus.error"
caption
class="text-negative q-mt-xs"
v-text="nostrStatus.error"
></q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<div class="col-auto">
<div class="inline-block">
<q-btn-dropdown
color="primary"
label="Publish"
icon="publish"
unelevated
:disable="stallCount === 0"
>
<q-list>
<q-item clickable v-close-popup @click="publishNip15">
<q-item-section avatar>
<q-icon name="store" />
</q-item-section>
<q-item-section>
<q-item-label>Publish NIP-15</q-item-label>
<q-item-label caption
>Publish stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="sell" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Publish NIP-99</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="refreshNip15">
<q-item-section avatar>
<q-icon name="refresh" />
</q-item-section>
<q-item-section>
<q-item-label>Refresh NIP-15 from Nostr</q-item-label>
<q-item-label caption
>Sync stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="refresh" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Refresh NIP-99 from Nostr</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="deleteNip15">
<q-item-section avatar>
<q-icon name="delete_forever" color="negative" />
</q-item-section>
<q-item-section>
<q-item-label class="text-negative"
>Delete NIP-15 from Nostr</q-item-label
>
<q-item-label caption
>Remove stalls and products</q-item-label
>
</q-item-section>
</q-item>
<q-item disable>
<q-item-section avatar>
<q-icon name="delete_forever" color="grey" />
</q-item-section>
<q-item-section>
<q-item-label class="text-grey"
>Delete NIP-99 from Nostr</q-item-label
>
<q-item-label caption
>Classified listings (coming soon)</q-item-label
>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-tooltip v-if="stallCount === 0">
First create a stall and add products.
</q-tooltip>
</div>
</div>
</div> </div>
</q-card-section> </q-card-section>
<q-card-section class="q-pa-none">
<q-list> {% include "nostrmarket/_api_docs.html" %} </q-list>
</q-card-section>
</q-card> </q-card>
</div> </div>
<div v-if="merchant && merchant.id" class="col-12"> <div v-if="merchant && merchant.id" class="col-12">
@ -376,6 +354,34 @@
> >
</direct-messages> </direct-messages>
</div> </div>
<div class="col-12">
<q-card>
<q-expansion-item
icon="info"
label="Details"
header-class="text-grey"
expand-separator
>
<q-img
src="/nostrmarket/static/market/images/nostr-cover.png"
:ratio="3"
fit="cover"
></q-img>
<q-card-section>
<div class="text-h6 q-mb-sm">Nostr Market</div>
<div class="text-body2 text-grey">
A decentralized marketplace extension for LNbits implementing the
NIP-15 protocol. Create stalls, list products, and accept
Lightning payments while communicating with customers via
encrypted Nostr direct messages.
</div>
</q-card-section>
<q-card-section class="q-pa-none">
<q-list> {% include "nostrmarket/_api_docs.html" %} </q-list>
</q-card-section>
</q-expansion-item>
</q-card>
</div>
</div> </div>
<div> <div>