market component (ready until final product object)

This commit is contained in:
Tiago Vasconcelos 2023-03-01 16:59:59 +00:00
parent 3733b24b8d
commit a215dc465d
4 changed files with 169 additions and 121 deletions

View file

@ -0,0 +1,83 @@
<div class="row q-col-gutter-md">
<div
class="col-xs-12 col-sm-6 col-md-4 col-lg-3"
v-for="item in products"
:key="item.id"
>
<q-card class="card--product">
<q-img
:src="item.image ? item.image : '/nostrmarket/static/images/placeholder.png'"
alt="Product Image"
loading="lazy"
spinner-color="white"
fit="contain"
height="300px"
></q-img>
<q-card-section class="q-pb-xs q-pt-md">
<div class="row no-wrap items-center">
<div class="col text-subtitle2 ellipsis-2-lines">
{{ item.product }}
</div>
</div>
<!-- <q-rating v-model="stars" color="orange" :max="5" readonly size="17px"></q-rating> -->
</q-card-section>
<q-card-section class="q-py-sm">
<div>
<div class="text-caption text-weight-bolder">
{{ item.stallName }}
</div>
<span v-if="item.currency == 'sat'">
<span class="text-h6">{{ item.price }} sats</span
><span class="q-ml-sm text-grey-6"
>BTC {{ (item.price / 1e8).toFixed(8) }}</span
>
</span>
<span v-else>
<span class="text-h6">{{ item.formatedPrice }}</span>
<span v-if="exchangeRates" class="q-ml-sm text-grey-6"
>({{ item.priceInSats }} sats)</span
>
</span>
<span
class="q-ml-md text-caption text-green-8 text-weight-bolder q-mt-md"
>{{ item.amount }} left</span
>
</div>
<div v-if="item.categories" class="text-subtitle1">
<q-chip v-for="(cat, i) in item.categories.split(',')" :key="i" dense
>{{cat}}</q-chip
>
</div>
<div
class="text-caption text-grey ellipsis-2-lines"
style="min-height: 40px"
>
<p v-if="item.description">{{ item.description }}</p>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-actions>
<span>Stall: {{ item.stallName }}</span>
<div class="q-ml-auto">
<q-btn flat dense>See product</q-btn>
<q-btn
flat
class="text-weight-bold text-capitalize q-ml-auto"
dense
color="primary"
type="a"
:href="'/market/stalls/' + item.stall"
target="_blank"
>
Visit Stall
</q-btn>
</div>
</q-card-actions>
</q-card>
</div>
</div>

View file

@ -0,0 +1,17 @@
async function customerMarket(path) {
const template = await loadTemplateAsync(path)
Vue.component('customer-market', {
name: 'customer-market',
template,
props: ['products', 'exchange-rates'],
data: function () {
return {}
},
methods: {
changePage() {
return
}
}
})
}

View file

@ -3,6 +3,7 @@
<q-drawer v-model="drawer" side="left"> <q-drawer v-model="drawer" side="left">
<q-toolbar class="bg-primary text-white shadow-2"> <q-toolbar class="bg-primary text-white shadow-2">
<q-toolbar-title>Settings</q-toolbar-title> <q-toolbar-title>Settings</q-toolbar-title>
<q-btn flat round dense icon="close" @click="drawer = !drawer"></q-btn>
</q-toolbar> </q-toolbar>
<div class="q-pa-md"> <div class="q-pa-md">
<q-list padding> <q-list padding>
@ -83,7 +84,7 @@
label="Relay URL" label="Relay URL"
hint="Add relays" hint="Add relays"
> >
<q-btn @click="" dense flat icon="add"></q-btn> <q-btn @click="addRelay" dense flat icon="add"></q-btn>
</q-input> </q-input>
<q-list dense class="q-mt-md"> <q-list dense class="q-mt-md">
<q-item v-for="url in Array.from(relays)" :key="url"> <q-item v-for="url in Array.from(relays)" :key="url">
@ -114,123 +115,42 @@
<q-page-container> <q-page-container>
<div class="row q-mb-md"> <div class="row q-mb-md">
<div class="col-12 q-gutter-y-md"> <div class="col-12 q-gutter-y-md">
<q-toolbar class="row"> <q-toolbar>
<div class="col"> <q-btn flat round dense icon="menu" @click="drawer = !drawer"></q-btn>
<q-toolbar-title> Market: </q-toolbar-title> {%raw%}
</div> <q-toolbar-title style="text-transform: capitalize">
<div class="col q-mx-md"> {{ activePage }}
<q-input </q-toolbar-title>
class="float-left full-width q-ml-md" {%endraw%}
standout <q-input
square class="float-left q-ml-md"
dense standout
outlined square
clearable dense
v-model.trim="searchText" outlined
label="Search for products" clearable
> v-model.trim="searchText"
<template v-slot:append> label="Search for products"
<q-icon v-if="!searchText" name="search" /> >
</template> <template v-slot:append>
</q-input> <q-icon v-if="!searchText" name="search" />
</div> </template>
</q-input>
</q-toolbar> </q-toolbar>
</div> </div>
</div> </div>
<div class="row q-col-gutter-md"> <customer-market
<div v-if="activePage == 'market'"
class="col-xs-12 col-sm-6 col-md-4 col-lg-3" :products="filterProducts"
v-for="item in filterProducts" :exchange-rates="exchangeRates"
:key="item.id" ></customer-market>
>
<q-card class="card--product">
{% raw %}
<q-img
:src="item.image ? item.image : '/nostrmarket/static/images/placeholder.png'"
alt="Product Image"
loading="lazy"
spinner-color="white"
fit="contain"
height="300px"
></q-img>
<q-card-section class="q-pb-xs q-pt-md">
<div class="row no-wrap items-center">
<div class="col text-subtitle2 ellipsis-2-lines">
{{ item.product }}
</div>
</div>
<!-- <q-rating v-model="stars" color="orange" :max="5" readonly size="17px"></q-rating> -->
</q-card-section>
<q-card-section class="q-py-sm">
<div>
<div class="text-caption text-weight-bolder">
{{ item.stallName }}
</div>
<span v-if="item.currency == 'sat'">
<span class="text-h6">{{ item.price }} sats</span
><span class="q-ml-sm text-grey-6"
>BTC {{ (item.price / 1e8).toFixed(8) }}</span
>
</span>
<span v-else>
<span class="text-h6"
>{{ getAmountFormated(item.price, item.currency) }}</span
>
<span v-if="exchangeRates" class="q-ml-sm text-grey-6"
>({{ getValueInSats(item.price, item.currency) }} sats)</span
>
</span>
<span
class="q-ml-md text-caption text-green-8 text-weight-bolder q-mt-md"
>{{item.quantity}} left</span
>
</div>
<div v-if="item.categories" class="text-subtitle1">
<q-chip
v-for="(cat, i) in item.categories.split(',')"
:key="i"
dense
>{{cat}}</q-chip
>
</div>
<div
class="text-caption text-grey ellipsis-2-lines"
style="min-height: 40px"
>
<p v-if="item.description">{{ item.description }}</p>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-actions>
<span>Stall: {{ item.stallName }}</span>
<q-btn
flat
class="text-weight-bold text-capitalize q-ml-auto"
dense
color="primary"
type="a"
:href="'/market/stalls/' + item.stall"
target="_blank"
>
Visit Stall
</q-btn>
</q-card-actions>
{% endraw %}
</q-card>
</div>
</div>
</q-page-container> </q-page-container>
<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-btn fab @click="drawer = !drawer" icon="menu" color="accent"></q-btn>
</q-page-sticky>
</q-layout> </q-layout>
{% endblock %} {% block scripts %} {% endblock %} {% block scripts %}
<script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script> <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/customer-market/customer-market.js') }}"></script>
<script> <script>
const nostr = window.NostrTools const nostr = window.NostrTools
const defaultRelays = [ const defaultRelays = [
@ -254,7 +174,13 @@
...Object.fromEntries(event.tags) ...Object.fromEntries(event.tags)
} }
} }
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
Promise.all([
customerMarket('static/components/customer-market/customer-market.html')
])
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
@ -270,7 +196,8 @@
searchText: null, searchText: null,
exchangeRates: null, exchangeRates: null,
inputPubkey: null, inputPubkey: null,
inputRelay: null inputRelay: null,
activePage: 'market'
} }
}, },
computed: { computed: {
@ -293,6 +220,16 @@
this.pubkeys.add( this.pubkeys.add(
'8f69ac99b96f7c4ad58b98cc38fe5d35ce02daefae7d1609c797ce3b4f92f5fd' '8f69ac99b96f7c4ad58b98cc38fe5d35ce02daefae7d1609c797ce3b4f92f5fd'
) )
// What component to render on start
let stall_id = JSON.parse('{{ stall_id | tojson }}')
let product_id = JSON.parse('{{ product_id | tojson }}')
if (stall_id) {
if (product_id) {
this.activePage = 'product'
} else {
this.activePage = 'stall'
}
}
this.$q.loading.show() this.$q.loading.show()
this.relays = new Set(defaultRelays) this.relays = new Set(defaultRelays)
await this.initNostr() await this.initNostr()
@ -320,17 +257,28 @@
return return
} else if (e.content.stall) { } else if (e.content.stall) {
//it's a product `d` is the prod. id //it's a product `d` is the prod. id
products.set(e.d, e.content) products.set(e.d, {...e.content, id: e.d})
} else { } else {
// it's a stall `d` is the stall id // it's a stall `d` is the stall id
stalls.set(e.d, e.content) stalls.set(e.d, {...e.content, id: e.d})
return return
} }
}) })
}) })
await Promise.resolve(sub) await Promise.resolve(sub)
this.products = Array.from(products.values()) this.stalls = await Array.from(stalls.values())
this.stalls = Array.from(stalls.values())
this.products = Array.from(products.values()).map(obj => {
obj.currency = 'EUR' // placeholder for testing/dev
let stall = this.stalls.find(s => s.id == obj.stall)
obj.stallName = stall.name
if (obj.currency != 'sat') {
obj.formatedPrice = this.getAmountFormated(obj.price, obj.currency)
obj.priceInSats = this.getValueInSats(obj.price, obj.currency)
}
return obj
})
pool.close(relays) pool.close(relays)
}, },
async getRates() { async getRates() {

View file

@ -1,7 +1,7 @@
import json import json
from http import HTTPStatus from http import HTTPStatus
from fastapi import Depends, Request from fastapi import Depends, Request, Query
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from loguru import logger from loguru import logger
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -23,12 +23,12 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@nostrmarket_ext.get("/market", response_class=HTMLResponse) @nostrmarket_ext.get("/market", response_class=HTMLResponse)
async def market(request: Request): async def market(
request: Request, stall_id: str = Query(None), product_id: str = Query(None)
):
return nostrmarket_renderer().TemplateResponse( return nostrmarket_renderer().TemplateResponse(
"nostrmarket/market.html", "nostrmarket/market.html",
{ {"request": request, "stall_id": stall_id, "product_id": product_id},
"request": request,
},
) )