From 1f017a7e094a4a2aeca0ed28ac6e2b76324d7127 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 14:45:45 +0000 Subject: [PATCH 01/29] initial files --- templates/nostrmarket/market.html | 202 ++++++++++++++++++++++++++++++ templates/nostrmarket/stall.html | 61 +++++++++ views.py | 18 +++ 3 files changed, 281 insertions(+) create mode 100644 templates/nostrmarket/market.html create mode 100644 templates/nostrmarket/stall.html diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html new file mode 100644 index 0000000..ef29755 --- /dev/null +++ b/templates/nostrmarket/market.html @@ -0,0 +1,202 @@ +{% extends "public.html" %} {% block page %} +
+
+ +
+ Market: +
+
+ + + +
+
+
+
+
+
+ + {% raw %} + + + +
+
+ {{ item.product }} +
+
+ + +
+ + +
+
+ {{ item.stallName }} +
+ + {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} + + + {{ getAmountFormated(item.price, item.currency) }} + ({{ getValueInSats(item.price, item.currency) }} sats) + + {{item.quantity}} left +
+
+ {{cat}} +
+
+

{{ item.description }}

+
+
+ + + + + Stall: {{ item.stallName }} + + Visit Stall + + + {% endraw %} +
+
+
+{% endblock %} {% block scripts %} + + +{% endblock %} diff --git a/templates/nostrmarket/stall.html b/templates/nostrmarket/stall.html new file mode 100644 index 0000000..208938c --- /dev/null +++ b/templates/nostrmarket/stall.html @@ -0,0 +1,61 @@ +{% extends "public.html" %} {% block page %} +
+{% endblock %} {% block scripts %} + + +{% endblock %} diff --git a/views.py b/views.py index ca8e1f7..47b07ff 100644 --- a/views.py +++ b/views.py @@ -20,3 +20,21 @@ async def index(request: Request, user: User = Depends(check_user_exists)): "nostrmarket/index.html", {"request": request, "user": user.dict()}, ) + + +@nostrmarket_ext.get("/market", response_class=HTMLResponse) +async def market(request: Request): + return nostrmarket_renderer().TemplateResponse( + "nostrmarket/market.html", + { + "request": request, + }, + ) + + +@nostrmarket_ext.get("/stall/{stall_id}", response_class=HTMLResponse) +async def stall(request: Request, stall_id: str): + return nostrmarket_renderer().TemplateResponse( + "nostrmarket/stall.html", + {"request": request, "stall_id": stall_id}, + ) From 59da80420788a571896a209a64601311754eb3ee Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 16:00:57 +0000 Subject: [PATCH 02/29] get nostr events and display --- templates/nostrmarket/market.html | 299 ++++++++++++++++++------------ 1 file changed, 182 insertions(+), 117 deletions(-) diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index ef29755..eb18f39 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -1,113 +1,162 @@ {% extends "public.html" %} {% block page %} -
-
- -
- Market: -
-
- - - -
+ + + + Settings -
-
-
-
- - {% raw %} - - - -
-
- {{ item.product }} -
-
- - -
- - -
-
- {{ item.stallName }} -
- - {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} - - - {{ getAmountFormated(item.price, item.currency) }} - ({{ getValueInSats(item.price, item.currency) }} sats) - - {{item.quantity}} left -
-
- {{cat}} -
-
+ + -

{{ item.description }}

-
-
- - - - - Stall: {{ item.stallName }} - + + Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem, + eius reprehenderit eos corrupti commodi magni quaerat ex numquam, + dolorum officiis modi facere maiores architecto suscipit iste + eveniet doloribus ullam aliquid. + +
+ + - Visit Stall - - - {% endraw %} - -
-
+ + + Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem, + eius reprehenderit eos corrupti commodi magni quaerat ex numquam, + dolorum officiis modi facere maiores architecto suscipit iste + eveniet doloribus ullam aliquid. + + + + + + + +
+
+ +
+ Market: +
+
+ + + +
+
+
+
+
+
+ + {% raw %} + + + +
+
+ {{ item.product }} +
+
+ + +
+ + +
+
+ {{ item.stallName }} +
+ + {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} + + + {{ getAmountFormated(item.price, item.currency) }} + ({{ getValueInSats(item.price, item.currency) }} sats) + + {{item.quantity}} left +
+
+ {{cat}} +
+
+

{{ item.description }}

+
+
+ + + + + Stall: {{ item.stallName }} + + Visit Stall + + + {% endraw %} +
+
+
+
+ + + + {% endblock %} {% block scripts %} From cd026a0a0b2e6b982e39936acedd5b300eddc076 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 17:06:24 +0000 Subject: [PATCH 03/29] menu add pubkey/npub --- templates/nostrmarket/market.html | 120 +++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 12 deletions(-) diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index eb18f39..33af581 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -14,10 +14,44 @@ > - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem, - eius reprehenderit eos corrupti commodi magni quaerat ex numquam, - dolorum officiis modi facere maiores architecto suscipit iste - eveniet doloribus ullam aliquid. + + + + + + + + + + + {%raw%} + + {{ `${pub.slice(0, 5)}...${pub.slice(-5)}` }}{{ pub }} + + + + + {%endraw%} + + @@ -29,10 +63,35 @@ > - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem, - eius reprehenderit eos corrupti commodi magni quaerat ex numquam, - dolorum officiis modi facere maiores architecto suscipit iste - eveniet doloribus ullam aliquid. + + + + + + {%raw%} + + {{ url }} + + + + + {%endraw%} + + @@ -190,11 +249,14 @@ return { drawer: true, pubkeys: new Set(), + relays: new Set(defaultRelays), stalls: [], products: [], events: [], searchText: null, - exchangeRates: null + exchangeRates: null, + inputPubkey: null, + inputRelay: null } }, computed: { @@ -220,7 +282,7 @@ async initNostr() { this.pool = new nostr.SimplePool() this.relays = new Set(defaultRelays) - await this.pool + let sub = await this.pool .list(Array.from(this.relays), [ { kinds: [30005], @@ -236,11 +298,12 @@ } else { // it's a stall this.stalls.push(e.content) + return } }) - console.log(this.stalls) - console.log(this.products) }) + await Promise.resolve(sub) + this.pool.close() }, async getRates() { let noFiat = this.stalls.map(s => s.currency).every(c => c == 'sat') @@ -260,8 +323,41 @@ }, getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) + }, + addPubkey() { + let pubkey = String(this.inputPubkey).trim() + let regExp = /^#([0-9a-f]{3}){1,2}$/i + if (regExp.test(pubkey)) { + return this.pubkeys.add(pubkey) + } + try { + let {type, data} = nostr.nip19.decode(pubkey) + if (type === 'npub') pubkey = data + else if (type === 'nprofile') { + pubkey = data.pubkey + givenRelays = data.relays + } + this.pubkeys.add(pubkey) + this.inputPubkey = null + } catch (err) { + console.error(err) + } + }, + addRelay() { + let relay = String(this.inputRelay).trim() + if (!relay.startsWith('ws')) { + console.debug('invalid url') + return + } + this.relays.add(relay) + this.inputRelay = null } } }) + {% endblock %} From 5b1f83d6f8f49562f15df227b8db3dba76657990 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 21:04:29 +0000 Subject: [PATCH 04/29] remove pubkeys and relays --- templates/nostrmarket/market.html | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 33af581..103d7db 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -1,6 +1,6 @@ {% extends "public.html" %} {% block page %} - - + + Settings @@ -47,6 +47,7 @@ dense round icon="delete" + @click="removePubkey(pub)" /> {%endraw%} @@ -87,6 +88,7 @@ dense round icon="delete" + @click="removeRelay(url)" /> {%endraw%} @@ -212,7 +214,7 @@ - + @@ -247,9 +249,9 @@ mixins: [windowMixin], data: function () { return { - drawer: true, + drawer: false, pubkeys: new Set(), - relays: new Set(defaultRelays), + relays: new Set(), stalls: [], products: [], events: [], @@ -269,6 +271,9 @@ p.categories.includes(this.searchText) ) }) + }, + relayList() { + return Array.from(this.relays) } }, async created() { @@ -276,12 +281,12 @@ this.pubkeys.add( '855ea22a88d7df7ccd8497777db81f115575d5362f51df3af02ead383f5eaba2' ) - //await this.initNostr() + this.relays = new Set(defaultRelays) + await this.initNostr() }, methods: { async initNostr() { this.pool = new nostr.SimplePool() - this.relays = new Set(defaultRelays) let sub = await this.pool .list(Array.from(this.relays), [ { @@ -343,6 +348,12 @@ console.error(err) } }, + removePubkey(pubkey) { + // Needs a hack for Vue reactivity + let pubkeys = this.pubkeys + pubkeys.delete(pubkey) + this.pubkeys = new Set(Array.from(pubkeys)) + }, addRelay() { let relay = String(this.inputRelay).trim() if (!relay.startsWith('ws')) { @@ -351,13 +362,14 @@ } this.relays.add(relay) this.inputRelay = null + }, + removeRelay(relay) { + // Needs a hack for Vue reactivity + let relays = this.relays + relays.delete(relay) + this.relays = new Set(Array.from(relays)) } } }) - {% endblock %} From f9f8b658433a2c9b4dffb3b132f1a672d3c1531e Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 22:38:50 +0000 Subject: [PATCH 05/29] fetch profiles --- templates/nostrmarket/market.html | 104 +++++++++++++++++++----------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 103d7db..a85f2fc 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -24,20 +24,31 @@ > - + + {%raw%} - + + - {%raw%} {{ `${pub.slice(0, 5)}...${pub.slice(-5)}` }}{{ pub }}{{ profiles.get(pub).name }} + {{ `${pub.slice(0, 5)}...${pub.slice(-5)}` + }} + {{ pub }} {% raw %} { this.events = events || [] this.events.map(eventToObj).map(e => { - if (e.content.stall) { - //it's a product - this.products.push(e.content) + if (e.kind == 0) { + this.profiles.set(e.pubkey, e.content) + return + } else if (e.content.stall) { + //it's a product `d` is the prod. id + products.set(e.d, e.content) } else { - // it's a stall - this.stalls.push(e.content) + // it's a stall `d` is the stall id + stalls.set(e.d, e.content) return } }) }) await Promise.resolve(sub) - this.pool.close() + this.products = Array.from(products.values()) + this.stalls = Array.from(stalls.values()) + pool.close(relays) }, async getRates() { let noFiat = this.stalls.map(s => s.currency).every(c => c == 'sat') @@ -329,32 +352,36 @@ getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) }, - addPubkey() { + async addPubkey() { let pubkey = String(this.inputPubkey).trim() let regExp = /^#([0-9a-f]{3}){1,2}$/i - if (regExp.test(pubkey)) { - return this.pubkeys.add(pubkey) - } - try { - let {type, data} = nostr.nip19.decode(pubkey) - if (type === 'npub') pubkey = data - else if (type === 'nprofile') { - pubkey = data.pubkey - givenRelays = data.relays + if (pubkey.startsWith('n')) { + try { + let {type, data} = nostr.nip19.decode(pubkey) + if (type === 'npub') pubkey = data + else if (type === 'nprofile') { + pubkey = data.pubkey + givenRelays = data.relays + } + this.pubkeys.add(pubkey) + this.inputPubkey = null + } catch (err) { + console.error(err) } - this.pubkeys.add(pubkey) - this.inputPubkey = null - } catch (err) { - console.error(err) + } else if (regExp.test(pubkey)) { + pubkey = pubkey } + this.pubkeys.add(pubkey) + await this.initNostr() }, removePubkey(pubkey) { // Needs a hack for Vue reactivity let pubkeys = this.pubkeys pubkeys.delete(pubkey) + this.profiles.delete(pubkey) this.pubkeys = new Set(Array.from(pubkeys)) }, - addRelay() { + async addRelay() { let relay = String(this.inputRelay).trim() if (!relay.startsWith('ws')) { console.debug('invalid url') @@ -362,6 +389,7 @@ } this.relays.add(relay) this.inputRelay = null + await this.initNostr() }, removeRelay(relay) { // Needs a hack for Vue reactivity From 3733b24b8d2ebd5f35f7981102a116f3942ee75e Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 28 Feb 2023 22:39:05 +0000 Subject: [PATCH 06/29] avatar picture --- static/images/blank-avatar.webp | Bin 0 -> 11090 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/images/blank-avatar.webp diff --git a/static/images/blank-avatar.webp b/static/images/blank-avatar.webp new file mode 100644 index 0000000000000000000000000000000000000000..513b0f3279d36569ead8f6553506c30fbb610703 GIT binary patch literal 11090 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 ASO5S3 literal 0 HcmV?d00001 From a215dc465dcab40861f37d1802dad0d2cbcb400f Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 1 Mar 2023 16:59:59 +0000 Subject: [PATCH 07/29] market component (ready until final product object) --- .../customer-market/customer-market.html | 83 ++++++++ .../customer-market/customer-market.js | 17 ++ templates/nostrmarket/market.html | 180 +++++++----------- views.py | 10 +- 4 files changed, 169 insertions(+), 121 deletions(-) create mode 100644 static/components/customer-market/customer-market.html create mode 100644 static/components/customer-market/customer-market.js diff --git a/static/components/customer-market/customer-market.html b/static/components/customer-market/customer-market.html new file mode 100644 index 0000000..99af972 --- /dev/null +++ b/static/components/customer-market/customer-market.html @@ -0,0 +1,83 @@ +
+
+ + + + +
+
+ {{ item.product }} +
+
+ + +
+ + +
+
+ {{ item.stallName }} +
+ + {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} + + + {{ item.formatedPrice }} + ({{ item.priceInSats }} sats) + + {{ item.amount }} left +
+
+ {{cat}} +
+
+

{{ item.description }}

+
+
+ + + + + Stall: {{ item.stallName }} +
+ See product + + Visit Stall + +
+
+
+
+
diff --git a/static/components/customer-market/customer-market.js b/static/components/customer-market/customer-market.js new file mode 100644 index 0000000..5844105 --- /dev/null +++ b/static/components/customer-market/customer-market.js @@ -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 + } + } + }) +} diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index a85f2fc..dba048d 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -3,6 +3,7 @@ Settings +
@@ -83,7 +84,7 @@ label="Relay URL" hint="Add relays" > - + @@ -114,123 +115,42 @@
- -
- Market: -
-
- - - -
+ + + {%raw%} + + {{ activePage }} + + {%endraw%} + + +
-
-
- - {% raw %} - - - -
-
- {{ item.product }} -
-
- - -
- - -
-
- {{ item.stallName }} -
- - {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} - - - {{ getAmountFormated(item.price, item.currency) }} - ({{ getValueInSats(item.price, item.currency) }} sats) - - {{item.quantity}} left -
-
- {{cat}} -
-
-

{{ item.description }}

-
-
- - - - - Stall: {{ item.stallName }} - - Visit Stall - - - {% endraw %} -
-
-
+
- - - {% endblock %} {% block scripts %} + + + + + + - - Vue.component(VueQrcode.name, VueQrcode) - - Promise.all([ - customerMarket('static/components/customer-market/customer-market.html'), - customerStall('static/components/customer-stall/customer-stall.html'), - productDetail('static/components/product-detail/product-detail.html') - ]) - - new Vue({ - el: '#vue', - mixins: [windowMixin], - data: function () { - return { - drawer: false, - pubkeys: new Set(), - relays: new Set(), - events: [], - stalls: [], - products: [], - profiles: new Map(), - searchText: null, - exchangeRates: null, - inputPubkey: null, - inputRelay: null, - activePage: 'market', - activeStall: null, - activeProduct: null - } - }, - computed: { - filterProducts() { - let products = this.products - if (this.activeStall) { - products = products.filter(p => p.stall == this.activeStall) - } - if (!this.searchText || this.searchText.length < 2) return products - return products.filter(p => { - return ( - p.name.includes(this.searchText) || - p.description.includes(this.searchText) || - p.categories.includes(this.searchText) - ) - }) - }, - stallName() { - return this.stalls.find(s => s.id == this.activeStall)?.name || 'Stall' - }, - productName() { - return ( - this.products.find(p => p.id == this.activeProduct)?.name || 'Product' - ) - } - }, - async created() { - // Check for stored merchants and relays on localStorage - try { - let merchants = this.$q.localStorage.getItem(`diagonAlley.merchants`) - let relays = this.$q.localStorage.getItem(`diagonAlley.relays`) - if (merchants && merchants.length) { - this.pubkeys = new Set(merchants) - } - if (relays && relays.length) { - this.relays = new Set([...defaultRelays, ...relays]) - } - } catch (e) { - console.error(e) - } - // Hardcode pubkeys for testing - /* - this.pubkeys.add( - 'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796' - ) - this.pubkeys.add( - '8f69ac99b96f7c4ad58b98cc38fe5d35ce02daefae7d1609c797ce3b4f92f5fd' - ) - */ - // stall ids S4hQgtTwiF5kGJZPbqMH9M jkCbdtkXeMjGBY3LBf8yn4 - let merchant_pubkey = JSON.parse('{{ merchant_pubkey | tojson }}') - let stall_id = JSON.parse('{{ stall_id | tojson }}') - let product_id = JSON.parse('{{ product_id | tojson }}') - if (merchant_pubkey) { - await addPubkey(merchant_pubkey) - /*LNbits.utils - .confirmDialog( - `We found a merchant pubkey in your request. Do you want to add it to the merchants list?` - ) - .onCancel(() => {}) - .onDismiss(() => {}) - .onOk(() => { - this.pubkeys.add(merchant_pubkey) - })*/ - } - this.$q.loading.show() - this.relays = new Set(defaultRelays) - // Get notes from Nostr - await this.initNostr() - - // What component to render on start - if (stall_id) { - if (product_id) { - this.activeProduct = product_id - } - this.activePage = 'stall' - this.activeStall = stall_id - } - - this.$q.loading.hide() - }, - methods: { - async initNostr() { - const pool = new nostr.SimplePool() - let relays = Array.from(this.relays) - let products = new Map() - let stalls = new Map() - // Get metadata and market data from the pubkeys - let sub = await pool - .list(relays, [ - { - kinds: [0, 30017, 30018], // for production kind is 30017 - authors: Array.from(this.pubkeys) - } - ]) - .then(events => { - console.log(events) - this.events = events || [] - this.events.map(eventToObj).map(e => { - if (e.kind == 0) { - this.profiles.set(e.pubkey, e.content) - return - } else if (e.kind == 30018) { - //it's a product `d` is the prod. id - products.set(e.d, {...e.content, id: e.d, categories: e.t}) - } else if (e.kind == 30017) { - // it's a stall `d` is the stall id - stalls.set(e.d, {...e.content, id: e.d, pubkey: e.pubkey}) - return - } - }) - }) - await Promise.resolve(sub) - this.stalls = await Array.from(stalls.values()) - - this.products = Array.from(products.values()).map(obj => { - let stall = this.stalls.find(s => s.id == obj.stall_id) - 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) - }, - async getRates() { - let noFiat = this.stalls.map(s => s.currency).every(c => c == 'sat') - if (noFiat) return - try { - let rates = await axios.get('https://api.opennode.co/v1/rates') - this.exchangeRates = rates.data.data - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, - navigateTo(page, opts = {stall: null, product: null, pubkey: null}) { - let {stall, product, pubkey} = opts - let url = new URL(window.location) - - if (pubkey) url.searchParams.set('merchant_pubkey', pubkey) - if (stall && !pubkey) { - pubkey = this.stalls.find(s => s.id == stall).pubkey - url.searchParams.set('merchant_pubkey', pubkey) - } - - switch (page) { - case 'stall': - if (stall) { - this.activeStall = stall - url.searchParams.set('stall_id', stall) - if (product) { - this.activeProduct = product - url.searchParams.set('product_id', product) - } - } - break - default: - this.activeStall = null - this.activeProduct = null - url.searchParams.delete('merchant_pubkey') - url.searchParams.delete('stall_id') - url.searchParams.delete('product_id') - break - } - - window.history.pushState({}, '', url) - this.activePage = page - }, - - getValueInSats(amount, unit = 'USD') { - if (!this.exchangeRates) return 0 - return Math.ceil( - (amount / this.exchangeRates[`BTC${unit}`][unit]) * 1e8 - ) - }, - getAmountFormated(amount, unit = 'USD') { - return LNbits.utils.formatCurrency(amount, unit) - }, - async addPubkey(pubkey = null) { - if (!pubkey) { - pubkey = String(this.inputPubkey).trim() - } - let regExp = /^#([0-9a-f]{3}){1,2}$/i - if (pubkey.startsWith('n')) { - try { - let {type, data} = nostr.nip19.decode(pubkey) - if (type === 'npub') pubkey = data - else if (type === 'nprofile') { - pubkey = data.pubkey - givenRelays = data.relays - } - this.pubkeys.add(pubkey) - this.inputPubkey = null - } catch (err) { - console.error(err) - } - } else if (regExp.test(pubkey)) { - pubkey = pubkey - } - this.pubkeys.add(pubkey) - this.$q.localStorage.set( - `diagonAlley.merchants`, - Array.from(this.pubkeys) - ) - await this.initNostr() - }, - removePubkey(pubkey) { - // Needs a hack for Vue reactivity - let pubkeys = this.pubkeys - pubkeys.delete(pubkey) - this.profiles.delete(pubkey) - this.pubkeys = new Set(Array.from(pubkeys)) - this.$q.localStorage.set( - `diagonAlley.merchants`, - Array.from(this.pubkeys) - ) - }, - async addRelay() { - let relay = String(this.inputRelay).trim() - if (!relay.startsWith('ws')) { - console.debug('invalid url') - return - } - this.relays.add(relay) - this.$q.localStorage.set(`diagonAlley.relays`, Array.from(this.relays)) - this.inputRelay = null - await this.initNostr() - }, - removeRelay(relay) { - // Needs a hack for Vue reactivity - let relays = this.relays - relays.delete(relay) - this.relays = new Set(Array.from(relays)) - this.$q.localStorage.set(`diagonAlley.relays`, Array.from(this.relays)) - } - } - }) - {% endblock %} From 8a2bc0e34563e1d05f7e349e35203d259a802608 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 3 Mar 2023 16:53:35 +0000 Subject: [PATCH 14/29] routing done --- .../customer-market/customer-market.html | 2 +- .../customer-market/customer-market.js | 6 +- .../customer-stall/customer-stall.html | 93 ++------------ .../customer-stall/customer-stall.js | 44 ++++--- .../components/product-card/product-card.html | 1 + .../components/product-card/product-card.js | 2 +- .../product-detail/product-detail.html | 118 +----------------- static/js/market.js | 76 +++++------ templates/nostrmarket/market.html | 3 +- 9 files changed, 78 insertions(+), 267 deletions(-) diff --git a/static/components/customer-market/customer-market.html b/static/components/customer-market/customer-market.html index 5de0b0d..ce52d87 100644 --- a/static/components/customer-market/customer-market.html +++ b/static/components/customer-market/customer-market.html @@ -10,7 +10,7 @@ v-for="(item, idx) in products" :key="idx" > - +
diff --git a/static/components/customer-market/customer-market.js b/static/components/customer-market/customer-market.js index 992222a..c14ffa9 100644 --- a/static/components/customer-market/customer-market.js +++ b/static/components/customer-market/customer-market.js @@ -8,7 +8,11 @@ async function customerMarket(path) { data: function () { return {} }, - methods: {}, + methods: { + changePageM(page, opts) { + this.$emit('change-page', page, opts) + } + }, created() {} }) } diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index 306a5f7..9bc302c 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -10,94 +10,25 @@ - + - +
+ +
+ +
+
- - - - - Add to cart -
-
- {{ item.name }} -
-
- - -
- - -
-
- {{ item.stallName }} -
- - {{ item.price }} satsBTC {{ (item.price / 1e8).toFixed(8) }} - - - {{ item.formatedPrice }} - ({{ item.priceInSats }} sats) - - {{ item.amount }} left -
-
- {{cat}} -
-
-

{{ item.description }}

-
-
- - - - - Stall: {{ item.stallName }} -
- See product -
-
-
+
diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 7606d68..1d9b395 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -1,34 +1,32 @@ async function customerStall(path) { const template = await loadTemplateAsync(path) - const mock = { - stall: '4M8j9KKGzUckHgb4C3pKCv', - name: 'product 1', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Leo integer malesuada nunc vel risus commodo. Sapien faucibus et molestie ac feugiat sed lectus vestibulum mattis. Cras ornare arcu dui vivamus. Risus pretium quam vulputate dignissim suspendisse in est ante in. Faucibus in ornare quam viverra orci sagittis eu volutpat odio.', - amount: 100, - price: '10', - images: ['https://i.imgur.com/cEfpEjq.jpeg'], - id: ['RyMbE9Hdwk9X333JKtkkNS'], - categories: ['crafts', 'robots'], - currency: 'EUR', - stallName: 'stall 1', - formatedPrice: '€10.00', - priceInSats: 0 - } + Vue.component('customer-stall', { name: 'customer-stall', template, - props: ['stall', 'products', 'exchange-rates', 'product-detail'], + props: [ + 'stall', + 'products', + 'exchange-rates', + 'product-detail', + 'change-page' + ], data: function () { - return { - mock: mock + return {} + }, + computed: { + product() { + if (this.productDetail) { + return this.products.find(p => p.id == this.productDetail) + } } }, - methods: {}, - created() { - console.log(this.stall) - console.log(this.products) - } + methods: { + changePageS(page, opts) { + this.$emit('change-page', page, opts) + } + }, + created() {} }) } diff --git a/static/components/product-card/product-card.html b/static/components/product-card/product-card.html index 5ff2782..93d4d96 100644 --- a/static/components/product-card/product-card.html +++ b/static/components/product-card/product-card.html @@ -52,6 +52,7 @@ Stall: {{ product.stallName }} + {{ $parent.activeStall }}
{{ product.amount > 0 ? 'In stock.' : 'Out of stock.' }} -
-
-
Customer rating
-
4.2
-
- -
-
(357 reviews)
-
- 93% would recommend to a friend -
- - - - 5 - - - 273 - - - 4 - - -   69 - - - 3 - - -      6 - - - 2 - - -      3 - - - 1 - - -      6 - - -
-
- -
diff --git a/static/js/market.js b/static/js/market.js index 3c823fc..6a0d723 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -30,7 +30,8 @@ const market = async () => { productCard('static/components/product-card/product-card.html'), customerMarket('static/components/customer-market/customer-market.html'), customerStall('static/components/customer-stall/customer-stall.html'), - productDetail('static/components/product-detail/product-detail.html') + productDetail('static/components/product-detail/product-detail.html'), + shoppingCart('static/components/shopping-cart/shopping-cart.html') ]) new Vue({ @@ -58,7 +59,7 @@ const market = async () => { filterProducts() { let products = this.products if (this.activeStall) { - products = products.filter(p => p.stall == this.activeStall) + products = products.filter(p => p.stall_id == this.activeStall) } if (!this.searchText || this.searchText.length < 2) return products return products.filter(p => { @@ -76,6 +77,9 @@ const market = async () => { return ( this.products.find(p => p.id == this.activeProduct)?.name || 'Product' ) + }, + isLoading() { + return this.$q.loading.isActive } }, async created() { @@ -88,51 +92,17 @@ const market = async () => { } if (relays && relays.length) { this.relays = new Set([...defaultRelays, ...relays]) + } else { + this.relays = new Set(defaultRelays) } } catch (e) { console.error(e) } - // Hardcode pubkeys for testing - /* - this.pubkeys.add( - 'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796' - ) - this.pubkeys.add( - '8f69ac99b96f7c4ad58b98cc38fe5d35ce02daefae7d1609c797ce3b4f92f5fd' - ) - */ - // stall ids S4hQgtTwiF5kGJZPbqMH9M jkCbdtkXeMjGBY3LBf8yn4 - /*let naddr = nostr.nip19.naddrEncode({ - identifier: '1234', - pubkey: - 'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796', - kind: 30018, - relays: defaultRelays - }) - console.log(naddr) - console.log(nostr.nip19.decode(naddr)) - */ + let params = new URLSearchParams(window.location.search) let merchant_pubkey = params.get('merchant_pubkey') let stall_id = params.get('stall_id') let product_id = params.get('product_id') - console.log(merchant_pubkey, stall_id, product_id) - if (merchant_pubkey) { - await addPubkey(merchant_pubkey) - /*LNbits.utils - .confirmDialog( - `We found a merchant pubkey in your request. Do you want to add it to the merchants list?` - ) - .onCancel(() => {}) - .onDismiss(() => {}) - .onOk(() => { - this.pubkeys.add(merchant_pubkey) - })*/ - } - this.$q.loading.show() - this.relays = new Set(defaultRelays) - // Get notes from Nostr - await this.initNostr() // What component to render on start if (stall_id) { @@ -142,11 +112,33 @@ const market = async () => { this.activePage = 'stall' this.activeStall = stall_id } + if (merchant_pubkey && !this.pubkeys.has(merchant_pubkey)) { + await LNbits.utils + .confirmDialog( + `We found a merchant pubkey in your request. Do you want to add it to the merchants list?` + ) + .onOk(async () => { + await this.addPubkey(merchant_pubkey) + }) + } + // Get notes from Nostr + await this.initNostr() this.$q.loading.hide() }, methods: { + naddr() { + let naddr = nostr.nip19.naddrEncode({ + identifier: '1234', + pubkey: + 'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796', + kind: 30018, + relays: defaultRelays + }) + console.log(naddr) + }, async initNostr() { + this.$q.loading.show() const pool = new nostr.SimplePool() let relays = Array.from(this.relays) let products = new Map() @@ -168,10 +160,10 @@ const market = async () => { return } else if (e.kind == 30018) { //it's a product `d` is the prod. id - products.set(e.d, {...e.content, id: e.d, categories: e.t}) + products.set(e.d, {...e.content, id: e.d[0], categories: e.t}) } else if (e.kind == 30017) { // it's a stall `d` is the stall id - stalls.set(e.d, {...e.content, id: e.d, pubkey: e.pubkey}) + stalls.set(e.d, {...e.content, id: e.d[0], pubkey: e.pubkey}) return } }) @@ -182,13 +174,13 @@ const market = async () => { this.products = Array.from(products.values()).map(obj => { let stall = this.stalls.find(s => s.id == obj.stall_id) obj.stallName = stall.name + obj.images = [obj.image] 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) }, async getRates() { diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 0d3c36e..c885e10 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -140,7 +140,7 @@ + {% endblock %} From 1b5079a88d31f53d4b2cce8151bfc6da7ab267f6 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 3 Mar 2023 16:53:46 +0000 Subject: [PATCH 15/29] init shopping cart --- .../components/shopping-cart/shopping-cart.html | 1 + static/components/shopping-cart/shopping-cart.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 static/components/shopping-cart/shopping-cart.html create mode 100644 static/components/shopping-cart/shopping-cart.js diff --git a/static/components/shopping-cart/shopping-cart.html b/static/components/shopping-cart/shopping-cart.html new file mode 100644 index 0000000..8650cc6 --- /dev/null +++ b/static/components/shopping-cart/shopping-cart.html @@ -0,0 +1 @@ + diff --git a/static/components/shopping-cart/shopping-cart.js b/static/components/shopping-cart/shopping-cart.js new file mode 100644 index 0000000..e0ad053 --- /dev/null +++ b/static/components/shopping-cart/shopping-cart.js @@ -0,0 +1,16 @@ +async function shoppingCart(path) { + const template = await loadTemplateAsync(path) + + Vue.component('shopping-cart', { + name: 'shopping-cart', + template, + + props: [], + data: function () { + return {} + }, + computed: {}, + methods: {}, + created() {} + }) +} From 7ae14aa32f143c85bac18b897e030c584d081e8d Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Sat, 4 Mar 2023 19:49:58 +0000 Subject: [PATCH 16/29] general fixes, prop passing and shopping cart (doesn't send dm) --- .../customer-stall/customer-stall.html | 106 ++++++++++- .../customer-stall/customer-stall.js | 167 +++++++++++++++++- .../components/product-card/product-card.html | 16 ++ .../components/product-card/product-card.js | 2 +- .../product-detail/product-detail.html | 12 +- .../product-detail/product-detail.js | 15 +- .../shopping-cart/shopping-cart.html | 52 +++++- .../components/shopping-cart/shopping-cart.js | 2 +- static/js/market.js | 17 +- templates/nostrmarket/market.html | 1 + templates/nostrmarket/stall.html | 61 ------- 11 files changed, 355 insertions(+), 96 deletions(-) delete mode 100644 templates/nostrmarket/stall.html diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index 9bc302c..aedec55 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -10,13 +10,20 @@ - +
@@ -28,7 +35,102 @@ v-for="(item, idx) in products" :key="idx" > - +
+ + + + + + + + + +
+
+ Generate key pair +
+
+ Get from Extension +
+
+ + +

Select the shipping zone:

+
+ +
+
+ Total: {{ stall.currency != 'sat' ? getAmountFormated(finalCost) : + finalCost + 'sats' }} + ({{ getValueInSats(finalCost) }} sats) +
+
+ Checkout + Cancel +
+
+
+
+ diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 1d9b395..858ed9d 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -10,23 +10,184 @@ async function customerStall(path) { 'products', 'exchange-rates', 'product-detail', - 'change-page' + 'change-page', + 'relays' ], data: function () { - return {} + return { + cart: { + total: 0, + size: 0, + products: new Map() + }, + cartMenu: [], + hasNip07: false, + checkoutDialog: { + show: false, + data: { + pubkey: null + } + }, + qrCodeDialog: { + data: { + payment_request: null + }, + show: false + } + } }, computed: { product() { if (this.productDetail) { return this.products.find(p => p.id == this.productDetail) } + }, + finalCost() { + if (!this.checkoutDialog.data.shippingzone) return this.cart.total + + let zoneCost = this.stall.shipping.find( + z => z.id == this.checkoutDialog.data.shippingzone + ) + return +this.cart.total + zoneCost.cost } }, methods: { changePageS(page, opts) { this.$emit('change-page', page, opts) + }, + getValueInSats(amount, unit = 'USD') { + if (!this.exchangeRates) return 0 + return Math.ceil( + (amount / this.exchangeRates[`BTC${unit}`][unit]) * 1e8 + ) + }, + getAmountFormated(amount, unit = 'USD') { + return LNbits.utils.formatCurrency(amount, unit) + }, + addToCart(item) { + console.log('add to cart', item) + let prod = this.cart.products + if (prod.has(item.id)) { + let qty = prod.get(item.id).quantity + prod.set(item.id, { + ...prod.get(item.id), + quantity: qty + 1 + }) + } else { + prod.set(item.id, { + name: item.name, + quantity: 1, + price: item.price, + image: item?.images[0] || null + }) + } + this.$q.notify({ + type: 'positive', + message: `${item.name} added to cart`, + icon: 'thumb_up' + }) + this.cart.products = prod + this.updateCart(+item.price) + }, + removeFromCart(item) { + this.cart.products.delete(item.id) + this.updateCart(+item.price, true) + }, + updateCart(price, del = false) { + console.log(this.cart, this.cartMenu) + if (del) { + this.cart.total -= price + this.cart.size-- + } else { + this.cart.total += price + this.cart.size++ + } + this.cartMenu = Array.from(this.cart.products, item => { + return {id: item[0], ...item[1]} + }) + console.log(this.cart, this.cartMenu) + }, + resetCart() { + this.cart = { + total: 0, + size: 0, + products: new Map() + } + }, + async getPubkey() { + try { + this.checkoutDialog.data.pubkey = await window.nostr.getPublicKey() + this.checkoutDialog.data.privkey = null + } catch (err) { + console.error( + `Failed to get a public key from a Nostr extension: ${err}` + ) + } + }, + generateKeyPair() { + let sk = NostrTools.generatePrivateKey() + let pk = NostrTools.getPublicKey(sk) + this.checkoutDialog.data.pubkey = pk + this.checkoutDialog.data.privkey = sk + }, + placeOrder() { + LNbits.utils + .confirmDialog( + `Send the order to the merchant? You should receive a message with the payment details.` + ) + .onOk(async () => { + let orderData = this.checkoutDialog.data + let content = { + name: orderData?.username, + description: null, + address: orderData.address, + message: null, + contact: { + nostr: orderData.pubkey, + phone: null, + email: orderData?.email + }, + items: Array.from(this.cart.products, p => { + return {product_id: p[0], quantity: p[1].quantity} + }) + } + let event = { + kind: 4, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: await window.nostr.nip04.encrypt( + orderData.pubkey, + content + ), + pubkey: orderData.pubkey + } + event.id = NostrTools.getEventHash(event) + if (orderData.privkey) { + event.sig = NostrTools.signEvent(event, orderData.privkey) + } else if (this.hasNip07) { + await window.nostr.signEvent(event) + } + await this.sendOrder(event) + }) + }, + async sendOrder(order) { + const pool = new NostrTools.SimplePool() + let relays = Array.from(this.relays) + let pubs = await pool.publish(relays, order) + pubs.on('ok', relay => { + console.log(`${relay.url} has accepted our event`) + }) + pubs.on('failed', reason => { + console.log(`failed to publish to ${reason}`) + }) } }, - created() {} + created() { + setTimeout(() => { + if (window.nostr) { + this.hasNip07 = true + } + }, 1000) + } }) } diff --git a/static/components/product-card/product-card.html b/static/components/product-card/product-card.html index 93d4d96..5a92ba3 100644 --- a/static/components/product-card/product-card.html +++ b/static/components/product-card/product-card.html @@ -9,6 +9,22 @@ >
+ Add to cart
{{ product.name }}
diff --git a/static/components/product-card/product-card.js b/static/components/product-card/product-card.js index 9e8490b..5e049df 100644 --- a/static/components/product-card/product-card.js +++ b/static/components/product-card/product-card.js @@ -4,7 +4,7 @@ async function productCard(path) { name: 'product-card', template, - props: ['product', 'change-page'], + props: ['product', 'change-page', 'add-to-cart', 'is-stall'], data: function () { return {} }, diff --git a/static/components/product-detail/product-detail.html b/static/components/product-detail/product-detail.html index 92fa1c0..be31eda 100644 --- a/static/components/product-detail/product-detail.html +++ b/static/components/product-detail/product-detail.html @@ -17,6 +17,11 @@ style="/*background-size: contain; background-repeat: no-repeat*/" > +
@@ -47,7 +52,7 @@ {{ product.amount > 0 ? 'In stock.' : 'Out of stock.' }}{{ product.quantity > 0 ? 'In stock.' : 'Out of stock.' }}
@@ -56,12 +61,13 @@ color="primary" icon="shopping_cart" label="Add to cart" + @click="$emit('add-to-cart', product)" />
diff --git a/static/components/product-detail/product-detail.js b/static/components/product-detail/product-detail.js index 7b60f6b..d55b653 100644 --- a/static/components/product-detail/product-detail.js +++ b/static/components/product-detail/product-detail.js @@ -4,23 +4,14 @@ async function productDetail(path) { name: 'product-detail', template, - props: ['product'], + props: ['product', 'add-to-cart'], data: function () { return { slide: 1 } }, - computed: { - win_width() { - return this.$q.screen.width - 59 - }, - win_height() { - return this.$q.screen.height - 0 - } - }, + computed: {}, methods: {}, - created() { - console.log('ping') - } + created() {} }) } diff --git a/static/components/shopping-cart/shopping-cart.html b/static/components/shopping-cart/shopping-cart.html index 8650cc6..2864cf3 100644 --- a/static/components/shopping-cart/shopping-cart.html +++ b/static/components/shopping-cart/shopping-cart.html @@ -1 +1,51 @@ - + + + {{ cart.size }} + + + + + + {{p.quantity}} x + + + + + + + + + {{ p.name }} + + + + + {{p.currency != 'sat' ? p.formatedPrice : p.price + 'sats'}} + + + + + + +
+ +
diff --git a/static/components/shopping-cart/shopping-cart.js b/static/components/shopping-cart/shopping-cart.js index e0ad053..8f6902d 100644 --- a/static/components/shopping-cart/shopping-cart.js +++ b/static/components/shopping-cart/shopping-cart.js @@ -5,7 +5,7 @@ async function shoppingCart(path) { name: 'shopping-cart', template, - props: [], + props: ['cart', 'cart-menu', 'remove-from-cart', 'reset-cart'], data: function () { return {} }, diff --git a/static/js/market.js b/static/js/market.js index 6a0d723..95bf5a6 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -1,20 +1,13 @@ const market = async () => { Vue.component(VueQrcode.name, VueQrcode) - const nostr = window.NostrTools + const NostrTools = window.NostrTools const defaultRelays = [ 'wss://relay.damus.io', 'wss://relay.snort.social', - 'wss://nos.lol', 'wss://nostr.wine', - 'wss://relay.nostr.bg', 'wss://nostr-pub.wellorder.net', - 'wss://nostr-pub.semisol.dev', - 'wss://eden.nostr.land', - 'wss://nostr.mom', - 'wss://nostr.fmt.wiz.biz', - 'wss://nostr.zebedee.cloud', - 'wss://nostr.rocks' + 'wss://nostr.zebedee.cloud' ] const eventToObj = event => { event.content = JSON.parse(event.content) @@ -128,7 +121,7 @@ const market = async () => { }, methods: { naddr() { - let naddr = nostr.nip19.naddrEncode({ + let naddr = NostrTools.nip19.naddrEncode({ identifier: '1234', pubkey: 'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796', @@ -139,7 +132,7 @@ const market = async () => { }, async initNostr() { this.$q.loading.show() - const pool = new nostr.SimplePool() + const pool = new NostrTools.SimplePool() let relays = Array.from(this.relays) let products = new Map() let stalls = new Map() @@ -243,7 +236,7 @@ const market = async () => { let regExp = /^#([0-9a-f]{3}){1,2}$/i if (pubkey.startsWith('n')) { try { - let {type, data} = nostr.nip19.decode(pubkey) + let {type, data} = NostrTools.nip19.decode(pubkey) if (type === 'npub') pubkey = data else if (type === 'nprofile') { pubkey = data.pubkey diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index c885e10..5f01c41 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -145,6 +145,7 @@ :products="filterProducts" :exchange-rates="exchangeRates" :product-detail="activeProduct" + :relays="relays" @change-page="navigateTo" > -{% endblock %} {% block scripts %} - - -{% endblock %} From ec1428cf6aff44d24fc882ce5bf256426ee2d4b2 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Sun, 5 Mar 2023 20:32:35 +0000 Subject: [PATCH 17/29] ordering working --- .../customer-stall/customer-stall.js | 138 +++++++++++------- static/js/market.js | 2 +- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 858ed9d..ce2be6e 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -24,9 +24,7 @@ async function customerStall(path) { hasNip07: false, checkoutDialog: { show: false, - data: { - pubkey: null - } + data: {} }, qrCodeDialog: { data: { @@ -130,56 +128,96 @@ async function customerStall(path) { this.checkoutDialog.data.pubkey = pk this.checkoutDialog.data.privkey = sk }, - placeOrder() { - LNbits.utils - .confirmDialog( - `Send the order to the merchant? You should receive a message with the payment details.` - ) - .onOk(async () => { - let orderData = this.checkoutDialog.data - let content = { - name: orderData?.username, - description: null, - address: orderData.address, - message: null, - contact: { - nostr: orderData.pubkey, - phone: null, - email: orderData?.email - }, - items: Array.from(this.cart.products, p => { - return {product_id: p[0], quantity: p[1].quantity} - }) - } - let event = { - kind: 4, - created_at: Math.floor(Date.now() / 1000), - tags: [], - content: await window.nostr.nip04.encrypt( - orderData.pubkey, - content - ), - pubkey: orderData.pubkey - } - event.id = NostrTools.getEventHash(event) - if (orderData.privkey) { - event.sig = NostrTools.signEvent(event, orderData.privkey) - } else if (this.hasNip07) { - await window.nostr.signEvent(event) - } - await this.sendOrder(event) + async placeOrder() { + // LNbits.utils + // .confirmDialog( + // `Send the order to the merchant? You should receive a message with the payment details.` + // ) + // .onOk(async () => { + let orderData = this.checkoutDialog.data + let orderObj = { + name: orderData?.username, + description: null, + address: orderData.address, + message: null, + contact: { + nostr: orderData.pubkey, + phone: null, + email: orderData?.email + }, + items: Array.from(this.cart.products, p => { + return {product_id: p[0], quantity: p[1].quantity} }) + } + let event = { + ...(await NostrTools.getBlankEvent()), + kind: 4, + created_at: Math.floor(Date.now() / 1000), + tags: [['p', this.stall.pubkey]], + pubkey: orderData.pubkey + } + if (orderData.privkey) { + event.content = await NostrTools.nip04.encrypt( + orderData.privkey, + this.stall.pubkey, + JSON.stringify(orderObj) + ) + } else { + console.log('use extension') + event.content = await window.nostr.nip04.encrypt( + orderData.pubkey, + JSON.stringify(orderObj) + ) + let userRelays = Object.keys( + (await window.nostr?.getRelays?.()) || [] + ) + if (userRelays.length != 0) { + userRelays.map(r => this.relays.add(r)) + } + } + event.id = NostrTools.getEventHash(event) + if (orderData.privkey) { + event.sig = await NostrTools.signEvent(event, orderData.privkey) + } else if (this.hasNip07) { + event = await window.nostr.signEvent(event) + } + console.log(event, orderData) + await this.sendOrder(event) + // }) }, async sendOrder(order) { - const pool = new NostrTools.SimplePool() - let relays = Array.from(this.relays) - let pubs = await pool.publish(relays, order) - pubs.on('ok', relay => { - console.log(`${relay.url} has accepted our event`) - }) - pubs.on('failed', reason => { - console.log(`failed to publish to ${reason}`) - }) + for (const url of Array.from(this.relays)) { + let relay = NostrTools.relayInit(url) + relay.on('connect', () => { + console.log(`connected to ${relay.url}`) + }) + relay.on('error', () => { + console.log(`failed to connect to ${relay.url}`) + }) + + await relay.connect() + let pub = relay.publish(order) + pub.on('ok', () => { + console.log(`${relay.url} has accepted our event`) + }) + pub.on('failed', reason => { + console.log(`failed to publish to ${relay.url}: ${reason}`) + }) + } + this.checkoutDialog = {show: false, data: {}} + // const pool = new NostrTools.SimplePool() + // let relays = Array.from(this.relays) + // try { + // let pubs = await pool.publish(relays, order) + // pubs.on('ok', relay => { + // console.log(`${relay.url} has accepted our event`) + // }) + // pubs.on('failed', (reason, err) => { + // console.log(`failed to publish to ${reason}: ${err}`) + // }) + // } catch (err) { + // console.error(err) + // } } }, created() { diff --git a/static/js/market.js b/static/js/market.js index 95bf5a6..a7c724e 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -5,7 +5,6 @@ const market = async () => { const defaultRelays = [ 'wss://relay.damus.io', 'wss://relay.snort.social', - 'wss://nostr.wine', 'wss://nostr-pub.wellorder.net', 'wss://nostr.zebedee.cloud' ] @@ -175,6 +174,7 @@ const market = async () => { return obj }) pool.close(relays) + return }, async getRates() { let noFiat = this.stalls.map(s => s.currency).every(c => c == 'sat') From a2e8c66f87f1b15da0c9b776cd42ca79b1dc40e4 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 6 Mar 2023 09:59:17 +0000 Subject: [PATCH 18/29] hash string --- static/js/utils.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/static/js/utils.js b/static/js/utils.js index 11ebc81..e684ab8 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -16,3 +16,13 @@ function loadTemplateAsync(path) { return result } + +async function hash(string) { + const utf8 = new TextEncoder().encode(string) + const hashBuffer = await crypto.subtle.digest('SHA-256', utf8) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hashHex = hashArray + .map(bytes => bytes.toString(16).padStart(2, '0')) + .join('') + return hashHex +} From dbaa9b9476ffbad901e7b35ab54503cf88266c58 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 6 Mar 2023 12:03:32 +0000 Subject: [PATCH 19/29] send/receive messages --- .../customer-stall/customer-stall.html | 7 + .../customer-stall/customer-stall.js | 239 +++++++++++------- 2 files changed, 159 insertions(+), 87 deletions(-) diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index aedec55..c6ac229 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -95,6 +95,13 @@ label="Email *optional" hint="Merchant may not use email" > +

Select the shipping zone:

{ - let orderData = this.checkoutDialog.data - let orderObj = { - name: orderData?.username, - description: null, - address: orderData.address, - message: null, - contact: { - nostr: orderData.pubkey, - phone: null, - email: orderData?.email - }, - items: Array.from(this.cart.products, p => { - return {product_id: p[0], quantity: p[1].quantity} + LNbits.utils + .confirmDialog( + `Send the order to the merchant? You should receive a message with the payment details.` + ) + .onOk(async () => { + let orderData = this.checkoutDialog.data + let orderObj = { + name: orderData?.username, + address: orderData.address, + message: orderData?.message, + contact: { + nostr: this.customerPubkey, + phone: null, + email: orderData?.email + }, + items: Array.from(this.cart.products, p => { + return {product_id: p[0], quantity: p[1].quantity} + }) + } + let created_at = Math.floor(Date.now() / 1000) + orderObj.id = await hash( + [this.customerPubkey, created_at, JSON.stringify(orderObj)].join( + ':' + ) + ) + let event = { + ...(await NostrTools.getBlankEvent()), + kind: 4, + created_at, + tags: [['p', this.stall.pubkey]], + pubkey: this.customerPubkey + } + if (this.customerPrivKey) { + event.content = await NostrTools.nip04.encrypt( + this.customerPrivKey, + this.stall.pubkey, + JSON.stringify(orderObj) + ) + } else { + event.content = await window.nostr.nip04.encrypt( + this.stall.pubkey, + JSON.stringify(orderObj) + ) + let userRelays = Object.keys( + (await window.nostr?.getRelays?.()) || [] + ) + if (userRelays.length != 0) { + userRelays.map(r => this.relays.add(r)) + } + } + event.id = NostrTools.getEventHash(event) + if (this.customerPrivKey) { + event.sig = await NostrTools.signEvent( + event, + this.customerPrivKey + ) + } else if (this.hasNip07) { + event = await window.nostr.signEvent(event) + } + console.log(event, orderObj) + await this.sendOrder(event) }) - } - let event = { - ...(await NostrTools.getBlankEvent()), - kind: 4, - created_at: Math.floor(Date.now() / 1000), - tags: [['p', this.stall.pubkey]], - pubkey: orderData.pubkey - } - if (orderData.privkey) { - event.content = await NostrTools.nip04.encrypt( - orderData.privkey, - this.stall.pubkey, - JSON.stringify(orderObj) - ) - } else { - console.log('use extension') - event.content = await window.nostr.nip04.encrypt( - orderData.pubkey, - JSON.stringify(orderObj) - ) - let userRelays = Object.keys( - (await window.nostr?.getRelays?.()) || [] - ) - if (userRelays.length != 0) { - userRelays.map(r => this.relays.add(r)) - } - } - event.id = NostrTools.getEventHash(event) - if (orderData.privkey) { - event.sig = await NostrTools.signEvent(event, orderData.privkey) - } else if (this.hasNip07) { - event = await window.nostr.signEvent(event) - } - console.log(event, orderData) - await this.sendOrder(event) - // }) }, async sendOrder(order) { for (const url of Array.from(this.relays)) { - let relay = NostrTools.relayInit(url) - relay.on('connect', () => { - console.log(`connected to ${relay.url}`) - }) - relay.on('error', () => { - console.log(`failed to connect to ${relay.url}`) - }) + try { + let relay = NostrTools.relayInit(url) + relay.on('connect', () => { + console.log(`connected to ${relay.url}`) + }) + relay.on('error', () => { + console.log(`failed to connect to ${relay.url}`) + }) - await relay.connect() - let pub = relay.publish(order) - pub.on('ok', () => { - console.log(`${relay.url} has accepted our event`) - }) - pub.on('failed', reason => { - console.log(`failed to publish to ${relay.url}: ${reason}`) - }) + await relay.connect() + let pub = relay.publish(order) + pub.on('ok', () => { + console.log(`${relay.url} has accepted our event`) + }) + pub.on('failed', reason => { + console.log(`failed to publish to ${relay.url}: ${reason}`) + }) + } catch (err) { + console.error(`Error: ${err}`) + } + } + this.resetCheckout() + this.listenMessages() + }, + async listenMessages() { + try { + const pool = new NostrTools.SimplePool() + const filters = [ + { + kinds: [4], + authors: [this.customerPubkey] + }, + { + kinds: [4], + '#p': [this.customerPubkey] + } + ] + let relays = Array.from(this.relays) + let subs = pool.sub(relays, filters) + subs.on('event', async event => { + let mine = event.pubkey == this.customerPubkey + let sender = mine + ? event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1] + : event.pubkey + if ( + (mine && sender != this.stall.pubkey) || + (!mine && sender != this.customerPubkey) + ) { + console.log(`Not relevant message!`) + return + } + try { + let plaintext = this.customerPrivKey + ? await NostrTools.nip04.decrypt( + this.customerPrivKey, + sender, + event.content + ) + : await window.nostr.nip04.decrypt(sender, event.content) + // console.log(`${mine ? 'Me' : 'Customer'}: ${plaintext}`) + this.nostrMessages.set(event.id, { + msg: plaintext, + timestamp: event.created_at, + sender: `${mine ? 'Me' : 'Merchant'}` + }) + } catch { + console.error('Unable to decrypt message!') + return + } + }) + } catch (err) { + console.error(`Error: ${err}`) } - this.checkoutDialog = {show: false, data: {}} - // const pool = new NostrTools.SimplePool() - // let relays = Array.from(this.relays) - // try { - // let pubs = await pool.publish(relays, order) - // pubs.on('ok', relay => { - // console.log(`${relay.url} has accepted our event`) - // }) - // pubs.on('failed', (reason, err) => { - // console.log(`failed to publish to ${reason}: ${err}`) - // }) - // } catch (err) { - // console.error(err) - // } } }, created() { From 46a9f9c01ee15a77352a2981ca52a24e8b4324ed Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 7 Mar 2023 13:25:16 +0000 Subject: [PATCH 20/29] login/account functionality --- static/js/market.js | 70 ++++++++++++++++- templates/nostrmarket/market.html | 120 ++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 3 deletions(-) diff --git a/static/js/market.js b/static/js/market.js index a7c724e..88042ce 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -2,6 +2,7 @@ const market = async () => { Vue.component(VueQrcode.name, VueQrcode) const NostrTools = window.NostrTools + const defaultRelays = [ 'wss://relay.damus.io', 'wss://relay.snort.social', @@ -23,7 +24,8 @@ const market = async () => { customerMarket('static/components/customer-market/customer-market.html'), customerStall('static/components/customer-stall/customer-stall.html'), productDetail('static/components/product-detail/product-detail.html'), - shoppingCart('static/components/shopping-cart/shopping-cart.html') + shoppingCart('static/components/shopping-cart/shopping-cart.html'), + chatDialog('static/components/chat-dialog/chat-dialog.html') ]) new Vue({ @@ -31,7 +33,15 @@ const market = async () => { mixins: [windowMixin], data: function () { return { - drawer: false, + account: null, + accountDialog: { + show: false, + data: { + watchOnly: false, + key: null + } + }, + drawer: true, pubkeys: new Set(), relays: new Set(), events: [], @@ -72,9 +82,20 @@ const market = async () => { }, isLoading() { return this.$q.loading.isActive + }, + hasExtension() { + return window.nostr + }, + isValidKey() { + return this.accountDialog.data.key + ?.toLowerCase() + ?.match(/^[0-9a-f]{64}$/) } }, async created() { + // Check for user stored + this.account = this.$q.localStorage.getItem('diagonAlley.account') || null + // Check for stored merchants and relays on localStorage try { let merchants = this.$q.localStorage.getItem(`diagonAlley.merchants`) @@ -115,7 +136,10 @@ const market = async () => { } // Get notes from Nostr - await this.initNostr() + //await this.initNostr() + + // Get fiat rates (i think there's an LNbits endpoint for this) + //await this.getRates() this.$q.loading.hide() }, methods: { @@ -129,6 +153,46 @@ const market = async () => { }) console.log(naddr) }, + async deleteAccount() { + await LNbits.utils + .confirmDialog( + `This will delete all stored data. If you didn't backup the Key Pair (Private and Public Keys), you will lose it. Continue?` + ) + .onOk(() => { + window.localStorage.removeItem('diagonAlley.account') + this.account = null + }) + }, + async createAccount(useExtension = false) { + let nip07 + if (useExtension) { + await this.getFromExtension() + nip07 = true + } + if (this.isValidKey) { + let {key, watchOnly} = this.accountDialog.data + this.$q.localStorage.set('diagonAlley.account', { + privkey: watchOnly ? null : key, + pubkey: watchOnly ? key : NostrTools.getPublicKey(key), + useExtension: nip07 ?? false + }) + this.accountDialog.data = { + watchOnly: false, + key: null + } + this.accountDialog.show = false + this.account = this.$q.localStorage.getItem('diagonAlley.account') + } + }, + generateKeyPair() { + this.accountDialog.data.key = NostrTools.generatePrivateKey() + this.accountDialog.data.watchOnly = false + }, + async getFromExtension() { + this.accountDialog.data.key = await window.nostr.getPublicKey() + this.accountDialog.data.watchOnly = true + return + }, async initNostr() { this.$q.loading.show() const pool = new NostrTools.SimplePool() diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 5f01c41..77c0cac 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -5,6 +5,32 @@ Settings +
+
+ + Delete account data +
+
+ Login or Create account +
+ +
+ + + + +
Account Setup
+ + +
+ +

Type your Nostr private key or generate a new one.

+ You can also use a Nostr-capable extension. +
+ + + + + + + + + + Is this a Public Key? + + If not using an Nostr capable extension, you'll have to sign + events manually! Better to use a Private Key that you can delete + later, or just generate an ephemeral key pair to use in the + Marketplace! + + + + + + + + + + +
+
{% endblock %} {% block scripts %} @@ -165,6 +247,44 @@ + + {% endblock %} From 5c852f11eb24ef1c1f8f32e657fe6b96b9b90669 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 10:01:03 +0000 Subject: [PATCH 21/29] remove exchange rates --- .../customer-market/customer-market.js | 2 +- .../customer-stall/customer-stall.html | 72 +++++++-- .../customer-stall/customer-stall.js | 144 +++++++++++------- .../components/product-card/product-card.html | 3 - .../product-detail/product-detail.html | 3 - static/js/market.js | 25 +-- static/js/utils.js | 12 ++ templates/nostrmarket/market.html | 3 +- 8 files changed, 163 insertions(+), 101 deletions(-) diff --git a/static/components/customer-market/customer-market.js b/static/components/customer-market/customer-market.js index c14ffa9..45d5bea 100644 --- a/static/components/customer-market/customer-market.js +++ b/static/components/customer-market/customer-market.js @@ -4,7 +4,7 @@ async function customerMarket(path) { name: 'customer-market', template, - props: ['products', 'exchange-rates', 'change-page'], + props: ['products', 'change-page'], data: function () { return {} }, diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index c6ac229..ea63043 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -10,6 +10,12 @@ + @@ -64,12 +71,12 @@ filled dense readonly - hint="This your key pair! Don't lose it!" - v-if="checkoutDialog.data.privkey" - v-model="checkoutDialog.data.privkey" + type="password" + v-if="customerPrivkey" + v-model="customerPrivkey" > -
+
- Total: {{ stall.currency != 'sat' ? getAmountFormated(finalCost) : - finalCost + 'sats' }} - ({{ getValueInSats(finalCost) }} sats) + > -->
+ +
+ +
+
+ + +
+ Copy invoice + Close +
+
+
diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index b72ba7d..4bd74c3 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -6,15 +6,16 @@ async function customerStall(path) { template, props: [ + 'account', 'stall', 'products', - 'exchange-rates', 'product-detail', 'change-page', 'relays' ], data: function () { return { + loading: false, cart: { total: 0, size: 0, @@ -23,8 +24,9 @@ async function customerStall(path) { cartMenu: [], hasNip07: false, customerPubkey: null, - customerPrivKey: null, - nostrMessages: new Map(), + customerPrivkey: null, + customerUseExtension: null, + activeOrder: null, checkoutDialog: { show: false, data: { @@ -58,12 +60,6 @@ async function customerStall(path) { changePageS(page, opts) { this.$emit('change-page', page, opts) }, - getValueInSats(amount, unit = 'USD') { - if (!this.exchangeRates) return 0 - return Math.ceil( - (amount / this.exchangeRates[`BTC${unit}`][unit]) * 1e8 - ) - }, getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) }, @@ -125,26 +121,12 @@ async function customerStall(path) { } } }, - async getPubkey() { - try { - this.customerPubkey = await window.nostr.getPublicKey() - this.checkoutDialog.data.pubkey = this.customerPubkey - this.checkoutDialog.data.privkey = null - } catch (err) { - console.error( - `Failed to get a public key from a Nostr extension: ${err}` - ) - } - }, - generateKeyPair() { - let sk = NostrTools.generatePrivateKey() - let pk = NostrTools.getPublicKey(sk) - this.customerPubkey = pk - this.customerPrivKey = sk - this.checkoutDialog.data.pubkey = this.customerPubkey - this.checkoutDialog.data.privkey = this.customerPrivKey + closeQrCodeDialog() { + this.qrCodeDialog.dismissMsg() + this.qrCodeDialog.show = false }, async placeOrder() { + this.loading = true LNbits.utils .confirmDialog( `Send the order to the merchant? You should receive a message with the payment details.` @@ -170,6 +152,7 @@ async function customerStall(path) { ':' ) ) + this.activeOrder = orderObj.id let event = { ...(await NostrTools.getBlankEvent()), kind: 4, @@ -177,13 +160,13 @@ async function customerStall(path) { tags: [['p', this.stall.pubkey]], pubkey: this.customerPubkey } - if (this.customerPrivKey) { + if (this.customerPrivkey) { event.content = await NostrTools.nip04.encrypt( - this.customerPrivKey, + this.customerPrivkey, this.stall.pubkey, JSON.stringify(orderObj) ) - } else { + } else if (this.customerUseExtension && this.hasNip07) { event.content = await window.nostr.nip04.encrypt( this.stall.pubkey, JSON.stringify(orderObj) @@ -196,15 +179,15 @@ async function customerStall(path) { } } event.id = NostrTools.getEventHash(event) - if (this.customerPrivKey) { + if (this.customerPrivkey) { event.sig = await NostrTools.signEvent( event, - this.customerPrivKey + this.customerPrivkey ) - } else if (this.hasNip07) { + } else if (this.customerUseExtension && this.hasNip07) { event = await window.nostr.signEvent(event) } - console.log(event, orderObj) + await this.sendOrder(event) }) }, @@ -223,25 +206,32 @@ async function customerStall(path) { let pub = relay.publish(order) pub.on('ok', () => { console.log(`${relay.url} has accepted our event`) + relay.close() }) pub.on('failed', reason => { console.log(`failed to publish to ${relay.url}: ${reason}`) + relay.close() }) } catch (err) { console.error(`Error: ${err}`) } } + this.loading = false this.resetCheckout() + this.resetCart() + this.qrCodeDialog.show = true + this.qrCodeDialog.dismissMsg = this.$q.notify({ + timeout: 0, + message: 'Waiting for invoice from merchant...' + }) this.listenMessages() }, async listenMessages() { + console.log('LISTEN') try { const pool = new NostrTools.SimplePool() const filters = [ - { - kinds: [4], - authors: [this.customerPubkey] - }, + // / { kinds: [4], '#p': [this.customerPubkey] @@ -254,38 +244,74 @@ async function customerStall(path) { let sender = mine ? event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1] : event.pubkey - if ( - (mine && sender != this.stall.pubkey) || - (!mine && sender != this.customerPubkey) - ) { - console.log(`Not relevant message!`) - return - } + try { - let plaintext = this.customerPrivKey - ? await NostrTools.nip04.decrypt( - this.customerPrivKey, - sender, - event.content - ) - : await window.nostr.nip04.decrypt(sender, event.content) - // console.log(`${mine ? 'Me' : 'Customer'}: ${plaintext}`) - this.nostrMessages.set(event.id, { - msg: plaintext, - timestamp: event.created_at, - sender: `${mine ? 'Me' : 'Merchant'}` - }) + let plaintext + if (this.customerPrivkey) { + plaintext = await NostrTools.nip04.decrypt( + this.customerPrivkey, + sender, + event.content + ) + } else if (this.customerUseExtension && this.hasNip07) { + plaintext = await window.nostr.nip04.decrypt( + sender, + event.content + ) + } + console.log(`${mine ? 'Me' : 'Merchant'}: ${plaintext}`) + + // this.nostrMessages.set(event.id, { + // msg: plaintext, + // timestamp: event.created_at, + // sender: `${mine ? 'Me' : 'Merchant'}` + // }) + this.messageFilter(plaintext, cb => Promise.resolve(pool.close)) } catch { console.error('Unable to decrypt message!') - return } }) } catch (err) { console.error(`Error: ${err}`) } + }, + messageFilter(text, cb = () => {}) { + if (!isJson(text)) return + let json = JSON.parse(text) + if (json.id != this.activeOrder) return + if (json?.payment_options) { + // this.qrCodeDialog.show = true + this.qrCodeDialog.data.payment_request = json.payment_options.find( + o => o.type == 'ln' + ).link + this.qrCodeDialog.dismissMsg = this.$q.notify({ + timeout: 0, + message: 'Waiting for payment...' + }) + } else if (json?.paid) { + this.qrCodeDialog.dismissMsg = this.$q.notify({ + type: 'positive', + message: 'Sats received, thanks!', + icon: 'thumb_up' + }) + this.closeQrCodeDialog() + this.activeOrder = null + Promise.resolve(cb()) + } else { + return + } } + // async mockInit() { + // this.customerPubkey = await window.nostr.getPublicKey() + // this.activeOrder = + // 'e4a16aa0198022dc682b2b52ed15767438282c0e712f510332fc047eaf795313' + // await this.listenMessages() + // } }, created() { + this.customerPubkey = this.account.pubkey + this.customerPrivkey = this.account.privkey + this.customerUseExtension = this.account.useExtension setTimeout(() => { if (window.nostr) { this.hasNip07 = true diff --git a/static/components/product-card/product-card.html b/static/components/product-card/product-card.html index 5a92ba3..95f86e2 100644 --- a/static/components/product-card/product-card.html +++ b/static/components/product-card/product-card.html @@ -43,9 +43,6 @@ {{ product.formatedPrice }} - ({{ product.priceInSats }} sats) {{ product.quantity }} left {{ product.formatedPrice }} - ({{ product.priceInSats }} sats) { key: null } }, - drawer: true, + drawer: false, pubkeys: new Set(), relays: new Set(), events: [], @@ -49,7 +49,6 @@ const market = async () => { products: [], profiles: new Map(), searchText: null, - exchangeRates: null, inputPubkey: null, inputRelay: null, activePage: 'market', @@ -136,10 +135,8 @@ const market = async () => { } // Get notes from Nostr - //await this.initNostr() + await this.initNostr() - // Get fiat rates (i think there's an LNbits endpoint for this) - //await this.getRates() this.$q.loading.hide() }, methods: { @@ -233,23 +230,12 @@ const market = async () => { obj.images = [obj.image] 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) return }, - async getRates() { - let noFiat = this.stalls.map(s => s.currency).every(c => c == 'sat') - if (noFiat) return - try { - let rates = await axios.get('https://api.opennode.co/v1/rates') - this.exchangeRates = rates.data.data - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, navigateTo(page, opts = {stall: null, product: null, pubkey: null}) { let {stall, product, pubkey} = opts let url = new URL(window.location) @@ -283,13 +269,6 @@ const market = async () => { window.history.pushState({}, '', url) this.activePage = page }, - - getValueInSats(amount, unit = 'USD') { - if (!this.exchangeRates) return 0 - return Math.ceil( - (amount / this.exchangeRates[`BTC${unit}`][unit]) * 1e8 - ) - }, getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) }, diff --git a/static/js/utils.js b/static/js/utils.js index e684ab8..c11cdef 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -26,3 +26,15 @@ async function hash(string) { .join('') return hashHex } + +function isJson(str) { + if (typeof str !== 'string') { + return false + } + try { + JSON.parse(str) + return true + } catch (error) { + return false + } +} diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 77c0cac..20a4332 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -169,15 +169,14 @@ v-if="!isLoading && activeStall" :stall="stalls.find(stall => stall.id == activeStall)" :products="filterProducts" - :exchange-rates="exchangeRates" :product-detail="activeProduct" :relays="relays" + :account="account" @change-page="navigateTo" > From 225ff162ab91224925a3ba2fe41ef16018312fc2 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 10:01:15 +0000 Subject: [PATCH 22/29] chat dialog component --- .../components/chat-dialog/chat-dialog.html | 89 +++++++++++ static/components/chat-dialog/chat-dialog.js | 143 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 static/components/chat-dialog/chat-dialog.html create mode 100644 static/components/chat-dialog/chat-dialog.js diff --git a/static/components/chat-dialog/chat-dialog.html b/static/components/chat-dialog/chat-dialog.html new file mode 100644 index 0000000..9ab5296 --- /dev/null +++ b/static/components/chat-dialog/chat-dialog.html @@ -0,0 +1,89 @@ +
+ + + + + +
Chat Box
+ + + + Minimize + + + Maximize + + + Close + +
+ + +
+
+ +
+ +
+
+ + + + + + + +
+
+
+
+
diff --git a/static/components/chat-dialog/chat-dialog.js b/static/components/chat-dialog/chat-dialog.js new file mode 100644 index 0000000..96f82f6 --- /dev/null +++ b/static/components/chat-dialog/chat-dialog.js @@ -0,0 +1,143 @@ +async function chatDialog(path) { + const template = await loadTemplateAsync(path) + + Vue.component('chat-dialog', { + name: 'chat-dialog', + template, + + props: ['account', 'merchant', 'relays'], + data: function () { + return { + dialog: false, + maximizedToggle: true, + pool: null, + nostrMessages: [], + newMessage: '' + } + }, + computed: { + sortedMessages() { + return this.nostrMessages.sort((a, b) => a.timestamp - b.timestamp) + } + }, + methods: { + async startPool() { + let sub = this.pool.sub(Array.from(this.relays), [ + { + kinds: [4], + authors: [this.account.pubkey] + }, + { + kinds: [4], + '#p': [this.account.pubkey] + } + ]) + sub.on('event', async event => { + let mine = event.pubkey == this.account.pubkey + let sender = mine + ? event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1] + : event.pubkey + + try { + let plaintext + if (this.account.privkey) { + plaintext = await NostrTools.nip04.decrypt( + this.account.privkey, + sender, + event.content + ) + } else if (this.account.useExtension && this.hasNip07) { + plaintext = await window.nostr.nip04.decrypt( + sender, + event.content + ) + } + this.nostrMessages.push({ + id: event.id, + msg: plaintext, + timestamp: event.created_at, + sender: `${mine ? 'Me' : 'Merchant'}` + }) + } catch { + console.error('Unable to decrypt message!') + } + }) + }, + async sendMessage() { + if (this.newMessage && this.newMessage.length < 1) return + let event = { + ...(await NostrTools.getBlankEvent()), + kind: 4, + created_at: Math.floor(Date.now() / 1000), + tags: [['p', this.merchant]], + pubkey: this.account.pubkey, + content: await this.encryptMsg() + } + event.id = NostrTools.getEventHash(event) + event.sig = this.signEvent(event) + for (const url of Array.from(this.relays)) { + try { + let relay = NostrTools.relayInit(url) + relay.on('connect', () => { + console.debug(`connected to ${relay.url}`) + }) + relay.on('error', () => { + console.debug(`failed to connect to ${relay.url}`) + }) + + await relay.connect() + let pub = relay.publish(event) + pub.on('ok', () => { + console.debug(`${relay.url} has accepted our event`) + relay.close() + }) + pub.on('failed', reason => { + console.debug(`failed to publish to ${relay.url}: ${reason}`) + relay.close() + }) + this.newMessage = '' + } catch (e) { + console.error(e) + } + } + }, + async encryptMsg() { + try { + let cypher + if (this.account.privkey) { + cypher = await NostrTools.nip04.encrypt( + this.account.privkey, + this.merchant, + this.newMessage + ) + } else if (this.account.useExtension && this.hasNip07) { + cypher = await window.nostr.nip04.encrypt( + this.merchant, + this.newMessage + ) + } + return cypher + } catch (e) { + console.error(e) + } + }, + async signEvent(event) { + if (this.account.privkey) { + event.sig = await NostrTools.signEvent(event, this.account.privkey) + } else if (this.account.useExtension && this.hasNip07) { + event = await window.nostr.signEvent(event) + } + return event + } + }, + created() { + this.pool = new NostrTools.SimplePool() + setTimeout(() => { + if (window.nostr) { + this.hasNip07 = true + } + }, 1000) + this.startPool() + } + }) +} From 9926cc857b63806fca4ff4a198dcf628195cb89c Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 11:23:51 +0000 Subject: [PATCH 23/29] clean up --- static/components/customer-stall/customer-stall.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index ea63043..5e2d085 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -121,9 +121,6 @@
Total: {{ stall.currency != 'sat' ? getAmountFormated(finalCost, stall.currency) : finalCost + 'sats' }} -
Date: Wed, 8 Mar 2023 11:24:11 +0000 Subject: [PATCH 24/29] fix add merchant pubkey --- static/js/market.js | 6 +++++- templates/nostrmarket/market.html | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/static/js/market.js b/static/js/market.js index 0077344..0d823e9 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -222,6 +222,7 @@ const market = async () => { }) }) await Promise.resolve(sub) + this.$q.loading.hide() this.stalls = await Array.from(stalls.values()) this.products = Array.from(products.values()).map(obj => { @@ -272,7 +273,8 @@ const market = async () => { getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) }, - async addPubkey(pubkey = null) { + async addPubkey(pubkey) { + console.log(pubkey, this.inputPubkey) if (!pubkey) { pubkey = String(this.inputPubkey).trim() } @@ -285,6 +287,7 @@ const market = async () => { pubkey = data.pubkey givenRelays = data.relays } + console.log(pubkey) this.pubkeys.add(pubkey) this.inputPubkey = null } catch (err) { @@ -310,6 +313,7 @@ const market = async () => { `diagonAlley.merchants`, Array.from(this.pubkeys) ) + Promise.resolve(this.initNostr()) }, async addRelay() { let relay = String(this.inputRelay).trim() diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index 20a4332..a5ebeb3 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -44,12 +44,12 @@ - + From c39eb2246dec5d7a62c7193de178efb0643131f2 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 15:56:39 +0000 Subject: [PATCH 25/29] better-ish chat box --- .../components/chat-dialog/chat-dialog.html | 118 ++++++++---------- static/components/chat-dialog/chat-dialog.js | 81 +++++++++++- 2 files changed, 124 insertions(+), 75 deletions(-) diff --git a/static/components/chat-dialog/chat-dialog.html b/static/components/chat-dialog/chat-dialog.html index 9ab5296..fd87273 100644 --- a/static/components/chat-dialog/chat-dialog.html +++ b/static/components/chat-dialog/chat-dialog.html @@ -1,9 +1,9 @@ -
- +
+ @@ -13,77 +13,57 @@
Chat Box
- - Minimize - - - Maximize - - + + Close - -
-
- -
- -
-
- - - - - - - -
+ + + + + + + + + + + +
diff --git a/static/components/chat-dialog/chat-dialog.js b/static/components/chat-dialog/chat-dialog.js index 96f82f6..e89e26d 100644 --- a/static/components/chat-dialog/chat-dialog.js +++ b/static/components/chat-dialog/chat-dialog.js @@ -9,7 +9,7 @@ async function chatDialog(path) { data: function () { return { dialog: false, - maximizedToggle: true, + loading: false, pool: null, nostrMessages: [], newMessage: '' @@ -17,11 +17,22 @@ async function chatDialog(path) { }, computed: { sortedMessages() { - return this.nostrMessages.sort((a, b) => a.timestamp - b.timestamp) + return this.nostrMessages.sort((a, b) => b.timestamp - a.timestamp) } }, methods: { + async startDialog() { + this.dialog = true + await this.startPool() + }, + async closeDialog() { + this.dialog = false + await this.pool.close(Array.from(this.relays)) + }, async startPool() { + this.loading = true + this.pool = new NostrTools.SimplePool() + let messagesMap = new Map() let sub = this.pool.sub(Array.from(this.relays), [ { kinds: [4], @@ -32,6 +43,10 @@ async function chatDialog(path) { '#p': [this.account.pubkey] } ]) + sub.on('eose', () => { + this.loading = false + this.nostrMessages = Array.from(messagesMap.values()) + }) sub.on('event', async event => { let mine = event.pubkey == this.account.pubkey let sender = mine @@ -52,8 +67,7 @@ async function chatDialog(path) { event.content ) } - this.nostrMessages.push({ - id: event.id, + messagesMap.set(event.id, { msg: plaintext, timestamp: event.created_at, sender: `${mine ? 'Me' : 'Merchant'}` @@ -62,6 +76,10 @@ async function chatDialog(path) { console.error('Unable to decrypt message!') } }) + setTimeout(() => { + this.nostrMessages = Array.from(messagesMap.values()) + this.loading = false + }, 5000) }, async sendMessage() { if (this.newMessage && this.newMessage.length < 1) return @@ -128,16 +146,67 @@ async function chatDialog(path) { event = await window.nostr.signEvent(event) } return event + }, + timeFromNow(time) { + // Get timestamps + let unixTime = new Date(time).getTime() + if (!unixTime) return + let now = new Date().getTime() + + // Calculate difference + let difference = unixTime / 1000 - now / 1000 + + // Setup return object + let tfn = {} + + // Check if time is in the past, present, or future + tfn.when = 'now' + if (difference > 0) { + tfn.when = 'future' + } else if (difference < -1) { + tfn.when = 'past' + } + + // Convert difference to absolute + difference = Math.abs(difference) + + // Calculate time unit + if (difference / (60 * 60 * 24 * 365) > 1) { + // Years + tfn.unitOfTime = 'years' + tfn.time = Math.floor(difference / (60 * 60 * 24 * 365)) + } else if (difference / (60 * 60 * 24 * 45) > 1) { + // Months + tfn.unitOfTime = 'months' + tfn.time = Math.floor(difference / (60 * 60 * 24 * 45)) + } else if (difference / (60 * 60 * 24) > 1) { + // Days + tfn.unitOfTime = 'days' + tfn.time = Math.floor(difference / (60 * 60 * 24)) + } else if (difference / (60 * 60) > 1) { + // Hours + tfn.unitOfTime = 'hours' + tfn.time = Math.floor(difference / (60 * 60)) + } else if (difference / 60 > 1) { + // Minutes + tfn.unitOfTime = 'minutes' + tfn.time = Math.floor(difference / 60) + } else { + // Seconds + tfn.unitOfTime = 'seconds' + tfn.time = Math.floor(difference) + } + + // Return time from now data + return `${tfn.time} ${tfn.unitOfTime}` } }, created() { - this.pool = new NostrTools.SimplePool() setTimeout(() => { if (window.nostr) { this.hasNip07 = true } }, 1000) - this.startPool() } }) } From c10a9bfaf9c0e7ea70433dcc907e325c3dbecef4 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 15:57:16 +0000 Subject: [PATCH 26/29] payment dialog flow --- .../customer-stall/customer-stall.html | 16 +++++++--------- .../components/customer-stall/customer-stall.js | 17 ++--------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index 5e2d085..1e06b29 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -7,7 +7,10 @@ @click="$emit('change-page', 'market')" style="cursor: pointer" > - + - -
- -
-
+ + +
diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 4bd74c3..58ab910 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -231,7 +231,6 @@ async function customerStall(path) { try { const pool = new NostrTools.SimplePool() const filters = [ - // / { kinds: [4], '#p': [this.customerPubkey] @@ -261,11 +260,6 @@ async function customerStall(path) { } console.log(`${mine ? 'Me' : 'Merchant'}: ${plaintext}`) - // this.nostrMessages.set(event.id, { - // msg: plaintext, - // timestamp: event.created_at, - // sender: `${mine ? 'Me' : 'Merchant'}` - // }) this.messageFilter(plaintext, cb => Promise.resolve(pool.close)) } catch { console.error('Unable to decrypt message!') @@ -280,7 +274,6 @@ async function customerStall(path) { let json = JSON.parse(text) if (json.id != this.activeOrder) return if (json?.payment_options) { - // this.qrCodeDialog.show = true this.qrCodeDialog.data.payment_request = json.payment_options.find( o => o.type == 'ln' ).link @@ -289,24 +282,18 @@ async function customerStall(path) { message: 'Waiting for payment...' }) } else if (json?.paid) { - this.qrCodeDialog.dismissMsg = this.$q.notify({ + this.closeQrCodeDialog() + this.$q.notify({ type: 'positive', message: 'Sats received, thanks!', icon: 'thumb_up' }) - this.closeQrCodeDialog() this.activeOrder = null Promise.resolve(cb()) } else { return } } - // async mockInit() { - // this.customerPubkey = await window.nostr.getPublicKey() - // this.activeOrder = - // 'e4a16aa0198022dc682b2b52ed15767438282c0e712f510332fc047eaf795313' - // await this.listenMessages() - // } }, created() { this.customerPubkey = this.account.pubkey From 8e5edd6e2b0a9a6b702dec2743534a020e2931e6 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 8 Mar 2023 15:57:34 +0000 Subject: [PATCH 27/29] tweaks --- static/js/market.js | 2 +- templates/nostrmarket/market.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/market.js b/static/js/market.js index 0d823e9..3cf9b90 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -222,7 +222,6 @@ const market = async () => { }) }) await Promise.resolve(sub) - this.$q.loading.hide() this.stalls = await Array.from(stalls.values()) this.products = Array.from(products.values()).map(obj => { @@ -234,6 +233,7 @@ const market = async () => { } return obj }) + this.$q.loading.hide() pool.close(relays) return }, diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index a5ebeb3..baa9715 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -57,7 +57,7 @@ {{ profiles.get(pub).name }} Date: Wed, 8 Mar 2023 21:05:53 +0000 Subject: [PATCH 28/29] brush up checkout dialog --- .../components/chat-dialog/chat-dialog.html | 1 - .../customer-stall/customer-stall.html | 94 +++++++++++++++---- .../customer-stall/customer-stall.js | 25 ++++- 3 files changed, 96 insertions(+), 24 deletions(-) diff --git a/static/components/chat-dialog/chat-dialog.html b/static/components/chat-dialog/chat-dialog.html index fd87273..1e46a4d 100644 --- a/static/components/chat-dialog/chat-dialog.html +++ b/static/components/chat-dialog/chat-dialog.html @@ -2,7 +2,6 @@
@@ -62,35 +62,77 @@ v-model.trim="checkoutDialog.data.username" label="Name *optional" > + + + + It seems you haven't logged in. You can: +
    +
  1. + enter your public and private keys bellow (to sign the order + message) +
  2. +
  3. use a Nostr Signer Extension (NIP07)
  4. +
  5. + fill out the required fields, without keys, and download the + order and send as a direct message to the merchant on any + Nostr client +
  6. +
+
+ + Use a Nostr browser extension + Download the order and send manually + +
+
+ -
Download Order + Checkout @@ -154,7 +208,7 @@ position="top" @hide="closeQrCodeDialog" > - +
diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 58ab910..b417c91 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -16,6 +16,7 @@ async function customerStall(path) { data: function () { return { loading: false, + isPwd: true, cart: { total: 0, size: 0, @@ -113,6 +114,24 @@ async function customerStall(path) { products: new Map() } }, + async downloadOrder() { + return + }, + async getFromExtension() { + this.customerPubkey = await window.nostr.getPublicKey() + this.customerUseExtension = true + this.checkoutDialog.data.pubkey = this.customerPubkey + }, + openCheckout() { + // Check if user is logged in + if (this.customerPubkey) { + this.checkoutDialog.data.pubkey = this.customerPubkey + if (this.customerPrivkey && !useExtension) { + this.checkoutDialog.data.privkey = this.customerPrivkey + } + } + this.checkoutDialog.show = true + }, resetCheckout() { this.checkoutDialog = { show: false, @@ -296,9 +315,9 @@ async function customerStall(path) { } }, created() { - this.customerPubkey = this.account.pubkey - this.customerPrivkey = this.account.privkey - this.customerUseExtension = this.account.useExtension + this.customerPubkey = this.account?.pubkey + this.customerPrivkey = this.account?.privkey + this.customerUseExtension = this.account?.useExtension setTimeout(() => { if (window.nostr) { this.hasNip07 = true From 448d243838fe25887d4964b479fde54b6bcb4ca1 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 9 Mar 2023 10:00:55 +0000 Subject: [PATCH 29/29] fixing vlad's review comments --- .../components/chat-dialog/chat-dialog.html | 2 +- static/components/chat-dialog/chat-dialog.js | 57 +------------------ static/js/market.js | 9 +-- static/js/utils.js | 54 ++++++++++++++++++ views.py | 14 +---- 5 files changed, 63 insertions(+), 73 deletions(-) diff --git a/static/components/chat-dialog/chat-dialog.html b/static/components/chat-dialog/chat-dialog.html index 1e46a4d..a428044 100644 --- a/static/components/chat-dialog/chat-dialog.html +++ b/static/components/chat-dialog/chat-dialog.html @@ -33,7 +33,7 @@ :text="[message.msg]" :sent="message.sender == 'Me'" :bg-color="message.sender == 'Me' ? 'white' : 'light-green-2'" - :stamp="`${timeFromNow(message.timestamp * 1000)}`" + :stamp="message.timestamp" size="6" /> diff --git a/static/components/chat-dialog/chat-dialog.js b/static/components/chat-dialog/chat-dialog.js index e89e26d..c111e27 100644 --- a/static/components/chat-dialog/chat-dialog.js +++ b/static/components/chat-dialog/chat-dialog.js @@ -69,7 +69,7 @@ async function chatDialog(path) { } messagesMap.set(event.id, { msg: plaintext, - timestamp: event.created_at, + timestamp: timeFromNow(event.created_at * 1000), sender: `${mine ? 'Me' : 'Merchant'}` }) } catch { @@ -107,11 +107,9 @@ async function chatDialog(path) { let pub = relay.publish(event) pub.on('ok', () => { console.debug(`${relay.url} has accepted our event`) - relay.close() }) pub.on('failed', reason => { console.debug(`failed to publish to ${relay.url}: ${reason}`) - relay.close() }) this.newMessage = '' } catch (e) { @@ -146,59 +144,6 @@ async function chatDialog(path) { event = await window.nostr.signEvent(event) } return event - }, - timeFromNow(time) { - // Get timestamps - let unixTime = new Date(time).getTime() - if (!unixTime) return - let now = new Date().getTime() - - // Calculate difference - let difference = unixTime / 1000 - now / 1000 - - // Setup return object - let tfn = {} - - // Check if time is in the past, present, or future - tfn.when = 'now' - if (difference > 0) { - tfn.when = 'future' - } else if (difference < -1) { - tfn.when = 'past' - } - - // Convert difference to absolute - difference = Math.abs(difference) - - // Calculate time unit - if (difference / (60 * 60 * 24 * 365) > 1) { - // Years - tfn.unitOfTime = 'years' - tfn.time = Math.floor(difference / (60 * 60 * 24 * 365)) - } else if (difference / (60 * 60 * 24 * 45) > 1) { - // Months - tfn.unitOfTime = 'months' - tfn.time = Math.floor(difference / (60 * 60 * 24 * 45)) - } else if (difference / (60 * 60 * 24) > 1) { - // Days - tfn.unitOfTime = 'days' - tfn.time = Math.floor(difference / (60 * 60 * 24)) - } else if (difference / (60 * 60) > 1) { - // Hours - tfn.unitOfTime = 'hours' - tfn.time = Math.floor(difference / (60 * 60)) - } else if (difference / 60 > 1) { - // Minutes - tfn.unitOfTime = 'minutes' - tfn.time = Math.floor(difference / 60) - } else { - // Seconds - tfn.unitOfTime = 'seconds' - tfn.time = Math.floor(difference) - } - - // Return time from now data - return `${tfn.time} ${tfn.unitOfTime}` } }, created() { diff --git a/static/js/market.js b/static/js/market.js index 3cf9b90..52fed25 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -62,12 +62,13 @@ const market = async () => { if (this.activeStall) { products = products.filter(p => p.stall_id == this.activeStall) } - if (!this.searchText || this.searchText.length < 2) return products + const searchText = this.searchText.toLowerCase() + if (!searchText || searchText.length < 2) return products return products.filter(p => { return ( - p.name.includes(this.searchText) || - p.description.includes(this.searchText) || - p.categories.includes(this.searchText) + p.name.toLowerCase().includes(searchText) || + p.description.toLowerCase().includes(searchText) || + p.categories.toLowerCase().includes(searchText) ) }) }, diff --git a/static/js/utils.js b/static/js/utils.js index c11cdef..7843723 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -38,3 +38,57 @@ function isJson(str) { return false } } + +function timeFromNow(time) { + // Get timestamps + let unixTime = new Date(time).getTime() + if (!unixTime) return + let now = new Date().getTime() + + // Calculate difference + let difference = unixTime / 1000 - now / 1000 + + // Setup return object + let tfn = {} + + // Check if time is in the past, present, or future + tfn.when = 'now' + if (difference > 0) { + tfn.when = 'future' + } else if (difference < -1) { + tfn.when = 'past' + } + + // Convert difference to absolute + difference = Math.abs(difference) + + // Calculate time unit + if (difference / (60 * 60 * 24 * 365) > 1) { + // Years + tfn.unitOfTime = 'years' + tfn.time = Math.floor(difference / (60 * 60 * 24 * 365)) + } else if (difference / (60 * 60 * 24 * 45) > 1) { + // Months + tfn.unitOfTime = 'months' + tfn.time = Math.floor(difference / (60 * 60 * 24 * 45)) + } else if (difference / (60 * 60 * 24) > 1) { + // Days + tfn.unitOfTime = 'days' + tfn.time = Math.floor(difference / (60 * 60 * 24)) + } else if (difference / (60 * 60) > 1) { + // Hours + tfn.unitOfTime = 'hours' + tfn.time = Math.floor(difference / (60 * 60)) + } else if (difference / 60 > 1) { + // Minutes + tfn.unitOfTime = 'minutes' + tfn.time = Math.floor(difference / 60) + } else { + // Seconds + tfn.unitOfTime = 'seconds' + tfn.time = Math.floor(difference) + } + + // Return time from now data + return `${tfn.time} ${tfn.unitOfTime}` +} diff --git a/views.py b/views.py index d83bc85..3b757fb 100644 --- a/views.py +++ b/views.py @@ -23,18 +23,8 @@ async def index(request: Request, user: User = Depends(check_user_exists)): @nostrmarket_ext.get("/market", response_class=HTMLResponse) -async def market( - request: Request, - stall_id: str = Query(None), - product_id: str = Query(None), - merchant_pubkey: str = Query(None), -): +async def market(request: Request): return nostrmarket_renderer().TemplateResponse( "nostrmarket/market.html", - { - "request": request, - "stall_id": stall_id, - "product_id": product_id, - "merchant_pubkey": merchant_pubkey, - }, + {"request": request}, )