Extract market place (#78)

* chore: remove marketplace components

* feat: add static marketplace

* feat: add entry point for static marketplace

* doc: add comment

* chore: include nostr-bundle.js
This commit is contained in:
Vlad Stan 2023-07-31 11:35:50 +03:00 committed by GitHub
parent 8ebe2fe458
commit a3299b63c4
60 changed files with 18008 additions and 3197 deletions

View file

@ -230,10 +230,7 @@
}
</style>
<!-- todo: serve locally -->
<script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script>
<script src="{{ url_for('nostrmarket_static', path='js/nostr.bundle.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='js/utils.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='components/key-pair/key-pair.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='components/shipping-zones/shipping-zones.js') }}"></script>

View file

@ -1,314 +1,36 @@
{% extends "public.html" %} {% block page %}
<q-layout view="lHh Lpr lff">
<q-page-container class="q-mr-md">
<div class="row q-mb-sm">
<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-pa-none">
<q-toolbar class="col-lg-1 col-md-1 col-sm-0 q-pl-none">
<q-avatar rounded size="64px" class="q-ma-none q-pa-none gt-sm">
<img v-if="logoImage" :src="logoImage">
</q-avatar>
</q-toolbar>
<q-toolbar class="col-lg-6 col-md-5 col-sm-12 auto-width">
<q-input class="rounded-pill" style="width:100%;" rounded outlined clearable v-model.trim="searchText"
label="Filter products, load market profile...">
<template v-slot:append>
<q-icon v-if="!searchText" name="search" />
</template>
</q-input>
</q-toolbar>
<q-toolbar class="col-lg-5 col-md-6 col-sm-12 q-ma-none">
<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" @click="setActivePage('user-config')" color="gray" icon="perm_identity" flat
size="lg"><q-tooltip>User
User Config</q-tooltip></q-btn>
<q-btn v-else @click="accountDialog.show = true" color="gray" icon="person_add" flat
size="lg"><q-tooltip>User
Login</q-tooltip></q-btn>
<q-btn @click="setActivePage('user-chat')" color="gray" icon="chat" flat
size="lg"><q-tooltip>Chat</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>
<!DOCTYPE html>
<html>
<q-badge v-if="allCartsItemCount" color="secondary" floating>
<span v-text="allCartsItemCount"></span>
</q-badge>
</q-btn>
</div>
<head>
<title>Nostr Market App</title>
<meta charset=utf-8>
<meta name=description content="A Nostr marketplace">
<meta name=format-detection content="telephone=no">
<meta name=msapplication-tap-highlight content=no>
<meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width">
</q-toolbar>
</div>
</div>
<div class="col-lg-2 col-md-1 col-sm-0"></div>
</div>
<div class="row q-mb-sm">
<div class="col-lg-2 col-md-1 col-sm-0"></div>
<div class="col-lg-8 col-md-10 col-sm-12">
<script src="{{ url_for('nostrmarket_static', path='market/js/nostr.bundle.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='market/js/bolt11-decoder.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='market/js/utils.js') }}"></script>
<div v-if="products?.length" class="gt-sm">
<q-virtual-scroll :items="allCategories" virtual-scroll-horizontal>
<template v-slot="{ item, index }">
<q-chip :key="index" :color="item.selected ? 'grey': ''" class="cursor-pointer q-mb-md">
<span v-text="item.category" @click="toggleCategoryFilter(item.category)"></span>
<q-badge @click="toggleCategoryFilter(item.category)" rounded class="q-ml-sm" color="secondary"> <span
v-text="item.count"></span></q-badge>
</q-chip>
</template>
</q-virtual-scroll>
</div>
</div>
<div class="col-lg-2 col-md-1 col-sm-0"></div>
</div>
<link rel=icon type=image/png sizes=128x128
href="{{ url_for('nostrmarket_static', path='market/icons/favicon-128x128.png')}}">
<div v-if="isLoading" class="row q-mb-sm">
<div class="col-12 text-center"> <q-spinner-dots color="primary" size="xl" /></div>
</div>
<link rel=icon type=image/png sizes=128x128
href="{{ url_for('nostrmarket_static', path='market/icons/favicon-96x96.png')}}">
<link rel=icon type=image/png sizes=128x128
href="{{ url_for('nostrmarket_static', path='market/icons/favicon-32x32.png')}}">
<link rel=icon type=image/png sizes=128x128 href="{{ url_for('nostrmarket_static', path='market/favicon.ico')}}">
<div class="row q-mb-sm">
<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">
<q-banner class="row q-pa-none q-mb-lg gt-sm shadow-2">
<q-img v-if="bannerImage" :src="bannerImage" class="rounded-borders" style="width: 100%; height: 250px" cover>
<div v-if="config?.opts?.about" class="absolute-bottom text-subtitle1 text-center">
<span v-text="config.opts.about"></span>
</div>
</q-img>
</q-banner>
<!-- Note: the .js and .css build IDs must be updated when a new version si released for 'static/market/index.html'-->
<script type="module" crossorigin
src="{{ url_for('nostrmarket_static', path='market/assets/index.725caa24.js')}}"></script>
<link rel="stylesheet" href="{{ url_for('nostrmarket_static', path='market/assets/index.5f2eed21.css')}}">
</head>
</div>
<div class="col-lg-2 col-md-1 col-sm-0 auto-width"></div>
</div>
<body>
<div id=q-app></div>
</body>
<div class="row q-mb-sm">
<div class="col-lg-2 col-md-1 col-sm-0"></div>
<div class="col-lg-7 col-md-9 col-sm-12 auto-width">
<q-breadcrumbs class="cursor q-mt-sm q-mr-sm">
<q-breadcrumbs-el :label="config?.opts?.name || 'Market'" icon="home" @click="navigateTo('market')"
class="cursor-pointer auto-width">
<q-checkbox v-model="groupByStall" v-if="activePage === 'market' && stalls?.length"
class="q-pl-md float-right" size="xs" val="xs" label="Group by stalls"></q-checkbox>
</q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'customer-stall'" :label="stallName"
@click="navigateTo('stall', {stall: activeStall})" icon="storefront"
class="cursor-pointer"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'customer-stall' && activeProduct" :label="productName"
class="cursor-pointer" icon="widgets"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'shopping-cart-list'" label="Shoping Cart"
icon="shopping_cart"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'customer-orders'" label="Orders"
icon="receipt_long"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'market-config'" label="Settings" icon="settings"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'user-config'" label="User Config"
icon="perm_identity"></q-breadcrumbs-el>
<q-breadcrumbs-el v-if="activePage === 'user-chat'" label="User Chat" icon="chat"></q-breadcrumbs-el>
</q-breadcrumbs>
</div>
<div class="col-lg-1 col-md-1 col-sm-0 auto-width">
<q-btn v-if="activePage === 'customer-stall'" flat color="grey" icon="content_copy" @click="copyUrl()"
class="float-right"></q-btn>
</div>
<div class="col-lg-2 col-md-1 col-sm-0 auto-width"></div>
</div>
<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">
<q-separator class="q-mt-sm q-mb-md"></q-separator>
<market-config v-if="activePage === 'market-config'" :merchants="merchants" @add-merchant="addMerchant"
@remove-merchant="removeMerchant" :relays="relays" :read-notes="readNotes" @add-relay="addRelay"
@remove-relay="removeRelay" :config-ui="config?.opts" @ui-config-update="updateUiConfig"
@publish-naddr="publishNaddr" @clear-all-data="clearAllData" @note-read="markNoteAsRead"></market-config>
<user-config v-else-if="activePage === 'user-config'" :account="account" @logout="logout"
@copy-text="copyText"></user-config>
<user-chat v-else-if="activePage === 'user-chat'"></user-chat>
<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="activePage === 'customer-stall'"
:stall="stalls.find(stall => stall.id == activeStall)" :products="filterProducts"
:product-detail="activeProduct" @change-page="navigateTo" @add-to-cart="addProductToCart"></customer-stall>
<div v-else-if="!merchants?.length">
<q-list class="q-mt-md" bordered>
<q-item>
<q-item-section avatar>
<q-avatar>
<q-icon color="primary" name="info" size="xl" />
</q-avatar>
</q-item-section>
<q-item-section class="q-mt-sm q-ml-lg">
<q-item-label><strong>Note</strong></q-item-label>
<q-item-label>
<div class="text-caption">
<span class="text-subtitle1"> You can start by adding a merchant public key </span>
<q-btn @click="setActivePage('market-config')" flat color="secondary" class="q-mb-xs">Here</q-btn>
<br>
<span class="text-subtitle1 q-pt-md">Or enter a nostr market profile ( <code>naddr</code>) in the
filter input.
</span>
</div>
</q-item-label>
</q-item-section>
<q-item-section side>
</q-item-section>
</q-item>
</q-list>
</div>
<div v-else>
<customer-stall-list v-if="groupByStall" :stalls="filterStalls"
@change-page="navigateTo"></customer-stall-list>
<customer-market v-else :filtered-products="filterProducts" :search-text="searchText"
:filter-categories="filterCategories" @change-page="navigateTo" @update-data="updateData"
@add-to-cart="addProductToCart"></customer-market>
</div>
</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>
<!-- 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/customer-stall-list/customer-stall-list.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='components/user-config/user-config.js') }}"></script>
<script src="{{ url_for('nostrmarket_static', path='components/user-chat/user-chat.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 %}
</html>