375 lines
No EOL
17 KiB
HTML
375 lines
No EOL
17 KiB
HTML
{% extends "public.html" %} {% block page %}
|
|
<q-layout view="lHh Lpr lff">
|
|
<!-- <q-header reveal bordered class="bg-marginal-bg">
|
|
<q-toolbar>
|
|
<q-btn flat @click="drawerLeft = !drawerLeft" round dense icon="menu" />
|
|
<q-toolbar-title>Header</q-toolbar-title>
|
|
<q-btn flat @click="drawerRight = !drawerRight" round dense icon="menu" />
|
|
</q-toolbar>
|
|
</q-header> -->
|
|
<!-- <q-drawer v-model="drawer" side="left"></q-drawer>
|
|
<q-drawer v-model="drawer" side="right"></q-drawer> -->
|
|
<!-- <q-drawer v-model="drawer" side="left">
|
|
<q-toolbar class="bg-primary text-white shadow-2">
|
|
<q-toolbar-title>Settings</q-toolbar-title>
|
|
<q-btn flat round dense icon="close" @click="drawer = !drawer"></q-btn>
|
|
</q-toolbar>
|
|
<div>
|
|
<div v-if="account" class="bg-transparent q-ma-md">
|
|
<q-avatar size="56px" class="q-mb-sm">
|
|
<img :src="accountMetadata?.picture || '/nostrmarket/static/images/blank-avatar.webp'" />
|
|
</q-avatar>
|
|
{%raw%}
|
|
<div class="text-weight-bold">
|
|
{{ `${account.pubkey.slice(0, 5)}...${account.pubkey.slice(-5)}` }}
|
|
</div>
|
|
<div v-if="accountMetadata && accountMetadata.name">
|
|
{{ accountMetadata.name }}
|
|
</div>
|
|
{%endraw%}
|
|
<q-btn label="Delete data" class="q-mt-md" color="red" @click="deleteAccount"><q-tooltip>Delete account
|
|
data</q-tooltip></q-btn>
|
|
</div>
|
|
<div v-else class="q-pa-md">
|
|
<q-btn label="Login" class="q-mt-md" color="primary" @click="accountDialog.show = true"><q-tooltip>Login or
|
|
Create account</q-tooltip></q-btn>
|
|
</div>
|
|
<q-separator></q-separator>
|
|
</div>
|
|
<div class="q-pa-md">
|
|
<q-list padding>
|
|
<q-expansion-item expand-separator icon="perm_identity" label="Merchants" caption="Add/Remove pubkeys">
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-input filled v-model="inputPubkey" @keydown.enter="addPubkey(null)" type="text" label="Pubkey/Npub"
|
|
hint="Add merchants">
|
|
<q-btn @click="addPubkey(null)" dense flat icon="add"></q-btn>
|
|
</q-input>
|
|
<q-list class="q-mt-md">
|
|
<q-item v-for="pub in Array.from(pubkeys)" :key="pub">
|
|
{%raw%}
|
|
<q-item-section avatar>
|
|
<q-avatar>
|
|
<img v-if="profiles.get(pub) && profiles.get(pub)?.picture" :src="profiles.get(pub).picture" />
|
|
<img v-else src="/nostrmarket/static/images/blank-avatar.webp" />
|
|
</q-avatar>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label v-if="profiles.get(pub) && profiles.get(pub)?.name">{{ profiles.get(pub).name
|
|
}}</q-item-label>
|
|
<q-item-label v-else>{{ `${pub.slice(0, 5)}...${pub.slice(-5)}`
|
|
}}</q-item-label>
|
|
<q-tooltip>{{ pub }}</q-tooltip>
|
|
</q-item-section>
|
|
<q-item-section side>
|
|
<q-btn class="gt-xs" size="12px" flat dense round icon="delete" @click="removePubkey(pub)" />
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-expansion-item>
|
|
<q-expansion-item expand-separator icon="perm_identity" label="Relays" caption="Add/Remove relays">
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-input filled v-model="inputRelay" @keydown.enter="addRelay" type="text" label="Relay URL"
|
|
hint="Add relays">
|
|
<q-btn @click="addRelay" dense flat icon="add"></q-btn>
|
|
</q-input>
|
|
<q-list dense class="q-mt-md">
|
|
<q-item v-for="url in Array.from(relays)" :key="url">
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>{{ url }}</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side>
|
|
<q-btn class="gt-xs" size="12px" flat dense round icon="delete" @click="removeRelay(url)" />
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-expansion-item>
|
|
<q-expansion-item expand-separator icon="perm_identity" label="Marketplace" caption="Marketplace info">
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-list class="q-mt-md">
|
|
<q-item-label header>Information</q-item-label>
|
|
<q-item>
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>Marketplace name</q-item-label>
|
|
<q-item-label caption>{{ config?.opts?.name }}</q-item-label>
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
<q-item>
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>About the marketplace</q-item-label>
|
|
<q-item-label caption>{{ config?.opts?.about }}</q-item-label>
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
<q-item-label header>UI Config</q-item-label>
|
|
<q-item>
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>Logo</q-item-label>
|
|
<q-item-label caption>{{ config?.opts?.ui?.picture }}</q-item-label>
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
<q-item>
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>Banner URL</q-item-label>
|
|
<q-item-label caption>{{ config?.opts?.ui?.banner }}</q-item-label>
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
<q-item>
|
|
{%raw%}
|
|
<q-item-section>
|
|
<q-item-label>Theme</q-item-label>
|
|
<q-item-label caption>{{ config?.opts?.ui?.theme }}</q-item-label>
|
|
</q-item-section>
|
|
{%endraw%}
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
<q-card-actions class="q-mx-md" align="right">
|
|
<q-btn v-if="naddr" label="Naddr" class="q-mt-md" color="primary" @click="copyText(naddr)"><q-tooltip>Copy
|
|
the
|
|
naddr for this configuration</q-tooltip></q-btn>
|
|
<q-btn v-if="naddr && canEditConfig" label="Edit" class="q-mt-md" color="primary"
|
|
@click="editConfigDialog"><q-tooltip>Edit configuration</q-tooltip></q-btn>
|
|
<q-btn label="New" class="q-mt-md" color="primary" @click="openConfigDialog"><q-tooltip>Create a new
|
|
configuration</q-tooltip></q-btn>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</q-expansion-item>
|
|
</q-list>
|
|
</div>
|
|
</q-drawer> -->
|
|
<!-- <q-drawer v-model="drawer" side="right"></q-drawer> -->
|
|
<q-page-container class="q-mr-md">
|
|
<div class="row q-mb-md">
|
|
<div class="col-lg-2 col-md-1 col-sm-0"></div>
|
|
<div class="col-lg-8 col-md-10 col-sm-12 auto-width">
|
|
|
|
<div class="row q-mb-md">
|
|
<q-toolbar class="col-lg-8 col-md-7 col-sm-6 auto-width">
|
|
<q-input class="rounded-pill" style="width:100%;" rounded outlined clearable v-model.trim="searchText"
|
|
label="Filter products">
|
|
<template v-slot:append>
|
|
<q-icon v-if="!searchText" name="search" />
|
|
</template>
|
|
</q-input>
|
|
</q-toolbar>
|
|
<q-toolbar class="col-lg-4 col-md-5 col-sm-6">
|
|
<div class="float-right">
|
|
<q-btn color="gray" icon="travel_explore" flat size="lg"
|
|
@click="setActivePage('search-nostr')"><q-tooltip>Search
|
|
for products on Nostr</q-tooltip></q-btn>
|
|
<q-btn color="gray" icon="settings" flat size="lg" @click="setActivePage('market-config')"><q-tooltip>
|
|
Settings</q-tooltip></q-btn>
|
|
<q-btn v-if="account" color="gray" icon="perm_identity" flat size="lg"><q-tooltip>User
|
|
Login</q-tooltip></q-btn>
|
|
<q-btn v-else @click="accountDialog.show = true" color="gray" icon="person_add" flat
|
|
size="lg"><q-tooltip>User
|
|
Config</q-tooltip></q-btn>
|
|
<q-btn @click="setActivePage('customer-orders')" color="gray" icon="receipt_long" flat
|
|
size="lg"><q-tooltip>Orders</q-tooltip></q-btn>
|
|
<q-btn color="gray" icon="shopping_cart" dense round flat size="lg"
|
|
@click="setActivePage('shopping-cart-list')">
|
|
<q-tooltip>Shopping Cart</q-tooltip>
|
|
|
|
<q-badge v-if="allCartsItemCount" color="secondary" floating>
|
|
<span v-text="allCartsItemCount"></span>
|
|
</q-badge>
|
|
</q-btn>
|
|
</div>
|
|
|
|
</q-toolbar>
|
|
|
|
</div>
|
|
<div class="q-mt-xl">
|
|
<q-breadcrumbs class="cursor">
|
|
<q-breadcrumbs-el :label="'Market'" icon="home" @click="navigateTo('market')"
|
|
class="cursor-pointer"></q-breadcrumbs-el>
|
|
<q-breadcrumbs-el v-if="activeStall" :label="stallName" @click="navigateTo('stall', {stall: activeStall})"
|
|
icon="storefront" class="cursor-pointer"></q-breadcrumbs-el>
|
|
<q-breadcrumbs-el v-if="activeProduct" :label="productName"
|
|
@click="navigateTo('product', {stall: activeStall, product: activeProduct})" class="cursor-pointer"
|
|
icon="widgets"></q-breadcrumbs-el>
|
|
</q-breadcrumbs>
|
|
</div>
|
|
|
|
<q-separator class="q-mt-md q-mb-md"></q-separator>
|
|
|
|
<market-config v-if="activePage === 'market-config'" :merchants="merchants" @add-merchant="addMerchant"
|
|
@remove-merchant="removeMerchant"></market-config>
|
|
<shopping-cart-list v-else-if="activePage === 'shopping-cart-list'" :carts="shoppingCarts"
|
|
@add-to-cart="addProductToCart" @remove-from-cart="removeProductFromCart" @remove-cart="removeCart"
|
|
@checkout-cart="checkoutStallCart"></shopping-cart-list>
|
|
<shopping-cart-checkout v-else-if="activePage === 'shopping-cart-checkout'" :cart="checkoutCart"
|
|
:stall="checkoutStall" :customer-pubkey="account?.pubkey" @login-required="openAccountDialog"
|
|
@place-order="placeOrder" @change-page="navigateTo"></shopping-cart-checkout>
|
|
<customer-orders v-else-if="activePage === 'customer-orders'" :orders="orders" :products="products"
|
|
:stalls="stalls" :merchants="merchants" @show-invoice="showInvoiceQr"></customer-orders>
|
|
<customer-stall v-else-if=" !isLoading && activeStall" :stall="stalls.find(stall => stall.id == activeStall)"
|
|
:products="filterProducts" :stall-products="products.filter(p => p.stall_id == activeStall)"
|
|
:product-detail="activeProduct" :relays="relays" :account="account" :pool="pool" :styles="config?.opts ?? {}"
|
|
@login-dialog="openAccountDialog" @change-page="navigateTo" @add-to-cart="addProductToCart"></customer-stall>
|
|
<customer-market v-else :search-nostr="searchNostr" :relays="relays" :products="products"
|
|
:search-text="searchText" :styles="config?.opts ?? {}" @change-page="navigateTo" @update-data="updateData"
|
|
@add-to-cart="addProductToCart"></customer-market>
|
|
</div>
|
|
<div class="col-lg-2 col-md-1 col-sm-0 auto-width"></div>
|
|
</div>
|
|
</q-page-container>
|
|
|
|
<!-- ACCOUNT DIALOG -->
|
|
<q-dialog v-model="accountDialog.show" position="top">
|
|
<q-card>
|
|
<q-card-section class="row">
|
|
<div class="text-h6">Account Setup</div>
|
|
<q-space></q-space>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<p>Enter your Nostr private key or generate a new one.</p>
|
|
</q-card-section>
|
|
|
|
<q-card-section class="q-pt-none">
|
|
<q-input dense label="Nsec/Hex" v-model="accountDialog.data.key" autofocus @keyup.enter="createAccount"
|
|
:error="accountDialog.data.key && !isValidAccountKey" hint="Enter you private key"></q-input>
|
|
</q-card-section>
|
|
|
|
<q-card-actions align="right" class="text-primary">
|
|
<q-btn v-if="isValidAccountKey" label="Login" color="primary" @click="() => createAccount()"></q-btn>
|
|
<q-btn v-else flat label="Generate" @click="generateKeyPair"></q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</q-dialog>
|
|
<!-- Config/Naddr Dialog -->
|
|
<q-dialog v-model="configDialog.show" position="top">
|
|
<q-card>
|
|
<q-card-section class="row items-center q-pb-none">
|
|
<div class="text-h6">Customize the Marketplace</div>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<p>Create an Nostr event with Market info</p>
|
|
<small> It will include all merchants on your merchants list and relays </small>
|
|
</q-card-section>
|
|
|
|
<q-card-section class="q-pt-none">
|
|
<q-form @submit="sendConfig" class="q-gutter-md">
|
|
<q-input filled dense v-model.trim="configDialog.data.name" label="Marketplace Name"></q-input>
|
|
<q-input filled dense v-model.trim="configDialog.data.about" label="Description"></q-input>
|
|
<p>Customize UI</p>
|
|
<q-input filled dense v-model.trim="configDialog.data.ui.picture" label="Marketplace Logo"
|
|
hint="URL to a logo image"></q-input>
|
|
<q-input filled dense v-model.trim="configDialog.data.ui.banner" label="Marketplace banner"
|
|
hint="URL to a header/banner image (max. 250px height)"></q-input>
|
|
<q-select filled dense v-model="configDialog.data.ui.theme" :options="g.allowedThemes" label="Theme">
|
|
<template v-slot:prepend>
|
|
<q-icon name="palette" />
|
|
</template>
|
|
</q-select>
|
|
<div class="row q-mt-lg">
|
|
<q-btn unelevated color="primary" type="submit">Publish</q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
<!-- END CHECKOUT DIALOG -->
|
|
<!-- INVOICE DIALOG -->
|
|
<q-dialog v-model="qrCodeDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<div class="text-center q-mb-lg">
|
|
<div v-if="qrCodeDialog.data.message" class="q-my-lg">
|
|
<strong><span v-text="qrCodeDialog.data.message"></span> </strong>
|
|
</div>
|
|
<a v-else :href="'lightning:' + qrCodeDialog.data?.payment_request">
|
|
<q-responsive v-if="qrCodeDialog.data.payment_request" :ratio="1" class="q-mx-xl">
|
|
<qrcode :value="qrCodeDialog.data.payment_request" :options="{width: 340}" class="rounded-borders"></qrcode>
|
|
</q-responsive>
|
|
<div v-else>
|
|
<q-spinner color="primary" size="2.55em"></q-spinner>
|
|
</div>
|
|
</a>
|
|
|
|
</div>
|
|
<div class="row q-mt-lg">
|
|
<q-btn v-if="qrCodeDialog.data.payment_request" outline color="grey"
|
|
@click="copyText(qrCodeDialog.data.payment_request)">Copy invoice</q-btn>
|
|
<q-btn flat v-close-popup color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
</q-layout>
|
|
{% endblock %} {% block scripts %}
|
|
<script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>
|
|
|
|
<script src="{{ url_for('nostrmarket_static', path='js/utils.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/product-card/product-card.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/customer-market/customer-market.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/customer-stall/customer-stall.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/product-detail/product-detail.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/shopping-cart/shopping-cart.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/shopping-cart-list/shopping-cart-list.js') }}"></script>
|
|
<script
|
|
src="{{ url_for('nostrmarket_static', path='components/shopping-cart-checkout/shopping-cart-checkout.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/customer-orders/customer-orders.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/chat-dialog/chat-dialog.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='components/market-config/market-config.js') }}"></script>
|
|
<script src="{{ url_for('nostrmarket_static', path='js/market.js') }}"></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 - 200px);*/
|
|
height: 70vh;
|
|
}
|
|
|
|
.chat-box {
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
padding: 1rem;
|
|
overflow-y: auto;
|
|
margin-left: auto;
|
|
width: 50%;
|
|
}
|
|
|
|
.chat-messages {
|
|
width: auto;
|
|
}
|
|
|
|
.chat-other {}
|
|
|
|
.chat-input {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: end;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.q-item__label--caption {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
</style>
|
|
{% endblock %} |