market component (ready until final product object)
This commit is contained in:
parent
3733b24b8d
commit
a215dc465d
4 changed files with 169 additions and 121 deletions
83
static/components/customer-market/customer-market.html
Normal file
83
static/components/customer-market/customer-market.html
Normal 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>
|
||||||
17
static/components/customer-market/customer-market.js
Normal file
17
static/components/customer-market/customer-market.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
10
views.py
10
views.py
|
|
@ -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,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue