diff --git a/README.md b/README.md index 95ff0d0..15c9ff1 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,28 @@ or by visiting `https:///nostrmarket/market` Make sure to add your `merchant` public key to the list: ![image](https://user-images.githubusercontent.com/2951406/236787686-0e300c0a-eb5d-4490-aa70-568738ac78f4.png) +### Styling +In order to create a customized Marketplace, we use `naddr` as defined in [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md#shareable-identifiers-with-extra-metadata). You must create an event (kind: `30019`) that has all the custom properties, including merchants and relays, of your marketplace. Start by going to the marketplace page: +![vanilla market](https://i.imgur.com/nCaMh1N.png) + +You'll need to Login, and head over to *Marketplace Info*. Optionally import some merchants and relays, that will be included in the event. Click on *Edit* and fill out your marketplace custom info: +![edit](https://i.imgur.com/wEuHuN9.png) + +Fill in the optional fields: +- Add a name to the Marketplace +- Add a small description +- Add a logo image URL +- Add a banner image URL (max height: 250px) +- Choose a theme + +By clicking *Publish*, a `kind: 30019` event will be sent to the defined relays containing all the information about your custom Marketplace. On the left drawer, a button with *Copy Naddr* will show up. +![copy naddr](https://i.imgur.com/VuNIMVf.png) + +You can then share your Marketplace, with the merchants and relays, banner, and style by using that Nostr identifier. The URL for the marketplace will be for example: `https://legend.lnbits.com/nostrmarket/market?naddr=naddr1qqfy6ctjddjhgurvv93k....`, you need to include the URL parameter `naddr=`. When a user visits that URL, the client will get the `30019` event and configure the Marketplace to what you defined. In the example bellow, a couple of merchants, relays, `autumn` theme, name (*Veggies Market*) and a header banner: +![final](https://i.imgur.com/EYG7vYS.png) + +The nostr event is a replaceable event, so you can change it to what you like and publish a new one to replace a previous one. For example adding a new merchant, or remove, change theme, add more relays,e tc... + ## Troubleshoot ### Check communication with Nostr diff --git a/static/components/customer-market/customer-market.html b/static/components/customer-market/customer-market.html index ee69136..eb056ff 100644 --- a/static/components/customer-market/customer-market.html +++ b/static/components/customer-market/customer-market.html @@ -22,44 +22,27 @@ - + + + + - +
-
+
-
+ \ No newline at end of file diff --git a/static/components/customer-market/customer-market.js b/static/components/customer-market/customer-market.js index f3335ba..ae76da7 100644 --- a/static/components/customer-market/customer-market.js +++ b/static/components/customer-market/customer-market.js @@ -10,7 +10,8 @@ async function customerMarket(path) { 'search-nostr', 'relays', 'update-products', - 'update-stalls' + 'update-stalls', + 'styles' ], data: function () { return { diff --git a/static/components/customer-stall/customer-stall.html b/static/components/customer-stall/customer-stall.html index a8c25fc..5787cd3 100644 --- a/static/components/customer-stall/customer-stall.html +++ b/static/components/customer-stall/customer-stall.html @@ -1,79 +1,35 @@
- - + + - - + +
- +
-
- +
+
- - + + It seems you haven't logged in. You can: @@ -91,131 +47,53 @@ - Use a Nostr browser extension - + Use a Nostr browser extension + - + - + - - - + + +

Select the shipping zone:

- +
Total: {{ stall.currency != 'sat' ? getAmountFormated(finalCost, stall.currency) : finalCost + 'sats' }}
- Download Order - Checkout - Cancel + Download Order + Checkout + Cancel
- +
@@ -223,12 +101,8 @@
- + @@ -236,15 +110,8 @@
- Copy invoice - Close + Copy invoice + Close
@@ -271,18 +138,12 @@
{{JSON.stringify(downloadOrderDialog.data, null, 2)}}
+ style="font-size: 0.65rem; overflow-x: auto">{{JSON.stringify(downloadOrderDialog.data, null, 2)}}
- Copy order + Copy order Close
-
+
\ No newline at end of file diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index b185d83..7a3b230 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -14,7 +14,8 @@ async function customerStall(path) { 'product-detail', 'change-page', 'relays', - 'pool' + 'pool', + 'styles' ], data: function () { return { @@ -31,6 +32,7 @@ async function customerStall(path) { customerPrivkey: null, customerUseExtension: null, activeOrder: null, + banner: null, checkoutDialog: { show: false, data: { @@ -269,7 +271,8 @@ async function customerStall(path) { items: Array.from(this.cart.products, p => { return {product_id: p[0], quantity: p[1].quantity} }), - shipping_id: orderData.shippingzone + shipping_id: orderData.shippingzone, + type: 0 } let created_at = Math.floor(Date.now() / 1000) orderObj.id = await hash( @@ -375,9 +378,8 @@ async function customerStall(path) { this.qrCodeDialog.data.message = json.message return cb() } - let payment_request = json.payment_options.find( - o => o.type == 'ln' - ).link + let payment_request = json.payment_options.find(o => o.type == 'ln') + .link if (!payment_request) return this.loading = false this.qrCodeDialog.data.payment_request = payment_request diff --git a/static/js/market.js b/static/js/market.js index 31b5b30..e50a891 100644 --- a/static/js/market.js +++ b/static/js/market.js @@ -61,7 +61,21 @@ const market = async () => { activePage: 'market', activeStall: null, activeProduct: null, - pool: null + pool: null, + config: null, + configDialog: { + show: false, + data: { + name: null, + about: null, + ui: { + picture: null, + banner: null, + theme: null + } + } + }, + naddr: null, } }, computed: { @@ -103,11 +117,15 @@ const market = async () => { key = data } return key?.toLowerCase()?.match(/^[0-9a-f]{64}$/) + }, + canEditConfig(){ + return this.account && this.account.pubkey == this.config?.pubkey } }, async created() { // Check for user stored this.account = this.$q.localStorage.getItem('diagonAlley.account') || null + //console.log("UUID", crypto.randomUUID()) // Check for stored merchants and relays on localStorage try { @@ -132,6 +150,23 @@ const market = async () => { let merchant_pubkey = params.get('merchant_pubkey') let stall_id = params.get('stall_id') let product_id = params.get('product_id') + let naddr = params.get('naddr') + + if(naddr) { + try { + let {type, data} = NostrTools.nip19.decode(naddr) + if(type == 'naddr' && data.kind == '30019') { // just double check + this.config = { + d: data.identifier, + pubkey: data.pubkey, + relays: data.relays + } + } + this.naddr = naddr + }catch (err){ + console.error(err) + } + } // What component to render on start if (stall_id) { @@ -204,6 +239,79 @@ const market = async () => { openAccountDialog() { this.accountDialog.show = true }, + editConfigDialog(){ + if(this.canEditConfig && this.config?.opts){ + let {name, about, ui} = this.config.opts + this.configDialog.data = {name, about, ui} + this.configDialog.data.identifier = this.config?.d + } + this.openConfigDialog() + }, + openConfigDialog() { + if(!this.account){ + this.$q.notify({ + message: `You need to be logged in first.`, + color: 'negative', + icon: 'warning' + }) + return + } + this.configDialog.show = true + }, + async sendConfig() { + let {name, about, ui} = this.configDialog.data + let merchants = Array.from(this.pubkeys) + let identifier = this.configDialog.data.identifier ?? crypto.randomUUID() + let event = { + ...(await NostrTools.getBlankEvent()), + kind: 30019, + content: JSON.stringify({name, about, ui, merchants}), + created_at: Math.floor(Date.now() / 1000), + tags: [['d', identifier]], + pubkey: this.account.pubkey + } + event.id = NostrTools.getEventHash(event) + try { + if(this.account.useExtension){ + event = await window.nostr.signEvent(event) + }else if (this.account.privkey) { + event.sig = await NostrTools.signEvent(event, this.account.privkey) + } + let pub = this.pool.publish(Array.from(this.relays), event) + pub.on('ok', () => console.debug(`Config event was sent`)) + pub.on('failed', error => console.error(error)) + } catch (err) { + console.error(err) + this.$q.notify({ + message: `Error signing event.`, + color: 'negative', + icon: 'warning' + }) + return + } + this.naddr = NostrTools.nip19.naddrEncode({ + pubkey: event.pubkey, + kind: 30019, + identifier: identifier, + relays: Array.from(this.relays) + }) + this.config = this.configDialog.data + this.resetConfig() + return + }, + resetConfig() { + this.configDialog = {show: false, + identifier: null, + data: { + name: null, + about: null, + ui: { + picture: null, + banner: null, + theme: null + } + }} + }, async updateData(events) { if (events.length < 1) { this.$q.notify({ @@ -254,6 +362,28 @@ const market = async () => { async initNostr() { this.$q.loading.show() const pool = new NostrTools.SimplePool() + + // If there is an naddr in the URL, get it and parse content + if (this.config) { + // add relays to the set + this.config.relays.forEach(r => this.relays.add(r)) + await pool.get(this.config.relays, { + kinds: [30019], + limit: 1, + authors: [this.config.pubkey], + '#d': [this.config.d] + }).then(event => { + if(!event) return + let content = JSON.parse(event.content) + this.config = {... this.config, opts: content} + // add merchants + this.config.opts?.merchants.forEach(m => this.pubkeys.add(m)) + // change theme + let {theme} = this.config.opts?.ui + theme && document.body.setAttribute('data-theme', theme) + }).catch(err => console.error(err)) + } + let relays = Array.from(this.relays) // Get metadata and market data from the pubkeys @@ -323,6 +453,15 @@ const market = async () => { window.history.pushState({}, '', url) this.activePage = page }, + copyText: function (text) { + var notify = this.$q.notify + Quasar.utils.copyToClipboard(text).then(function () { + notify({ + message: 'Copied to clipboard!', + position: 'bottom' + }) + }) + }, getAmountFormated(amount, unit = 'USD') { return LNbits.utils.formatCurrency(amount, unit) }, diff --git a/templates/nostrmarket/market.html b/templates/nostrmarket/market.html index fc3c3d2..8b25525 100644 --- a/templates/nostrmarket/market.html +++ b/templates/nostrmarket/market.html @@ -8,9 +8,7 @@
- + {%raw%}
@@ -20,43 +18,22 @@ {{ accountMetadata.name }}
{%endraw%} - Delete account data + Delete account + data
- Login or Create account + Login or + Create account
- + - + @@ -64,37 +41,19 @@ {%raw%} - - + + - {{ profiles.get(pub).name }} - {{ `${pub.slice(0, 5)}...${pub.slice(-5)}` - }} + {{ profiles.get(pub).name + }} + {{ `${pub.slice(0, 5)}...${pub.slice(-5)}` + }} {{ pub }} - + {%endraw%} @@ -102,22 +61,11 @@ - + - + @@ -127,15 +75,7 @@ {{ url }} - + {%endraw%} @@ -143,6 +83,65 @@ + + + + + Information + + {%raw%} + + Marketplace name + {{ config?.opts?.name }} + + {%endraw%} + + + {%raw%} + + About the marketplace + {{ config?.opts?.about }} + + {%endraw%} + + UI Config + + {%raw%} + + Logo + {{ config?.opts?.ui?.picture }} + + {%endraw%} + + + {%raw%} + + Banner URL + {{ config?.opts?.ui?.banner }} + + {%endraw%} + + + {%raw%} + + Theme + {{ config?.opts?.ui?.theme }} + + {%endraw%} + + + + + Copy + the + naddr for this configuration + Edit configuration + Create a new + configuration + + +
@@ -157,24 +156,10 @@ {%endraw%} - Search for products on Nostr - + Search for products on Nostr + @@ -182,26 +167,12 @@ - - + + @@ -217,15 +188,9 @@ - + hint="Enter you private key (recommended) or public key"> @@ -244,22 +209,45 @@ - - + + + + + + +
Customize the Marketplace
+
+ +

Create an Nostr event with Market info

+ It will include all merchants on your merchants list and relays +
+ + + + + +

Customize UI

+ + + + + +
+ Publish + Cancel +
+
+
+
{% endblock %} {% block scripts %} @@ -300,8 +288,7 @@ width: auto; } - .chat-other { - } + .chat-other {} .chat-input { position: relative; @@ -309,5 +296,11 @@ align-items: end; margin-top: 1rem; } + + .q-item__label--caption { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } -{% endblock %} +{% endblock %} \ No newline at end of file