Merge pull request #158 from BenGWeeks/feature/merchant-keys-panel-146
feat: improve merchant profile panel UI (#146)
This commit is contained in:
commit
20dc241c89
13 changed files with 775 additions and 174 deletions
27
models.py
27
models.py
|
|
@ -33,8 +33,13 @@ class Nostrable:
|
||||||
|
|
||||||
class MerchantProfile(BaseModel):
|
class MerchantProfile(BaseModel):
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
|
display_name: str | None = None
|
||||||
about: str | None = None
|
about: str | None = None
|
||||||
picture: str | None = None
|
picture: str | None = None
|
||||||
|
banner: str | None = None
|
||||||
|
website: str | None = None
|
||||||
|
nip05: str | None = None
|
||||||
|
lud16: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class MerchantConfig(MerchantProfile):
|
class MerchantConfig(MerchantProfile):
|
||||||
|
|
@ -86,11 +91,23 @@ class Merchant(PartialMerchant, Nostrable):
|
||||||
return merchant
|
return merchant
|
||||||
|
|
||||||
def to_nostr_event(self, pubkey: str) -> NostrEvent:
|
def to_nostr_event(self, pubkey: str) -> NostrEvent:
|
||||||
content = {
|
content: dict[str, str] = {}
|
||||||
"name": self.config.name,
|
if self.config.name:
|
||||||
"about": self.config.about,
|
content["name"] = self.config.name
|
||||||
"picture": self.config.picture,
|
if self.config.display_name:
|
||||||
}
|
content["display_name"] = self.config.display_name
|
||||||
|
if self.config.about:
|
||||||
|
content["about"] = self.config.about
|
||||||
|
if self.config.picture:
|
||||||
|
content["picture"] = self.config.picture
|
||||||
|
if self.config.banner:
|
||||||
|
content["banner"] = self.config.banner
|
||||||
|
if self.config.website:
|
||||||
|
content["website"] = self.config.website
|
||||||
|
if self.config.nip05:
|
||||||
|
content["nip05"] = self.config.nip05
|
||||||
|
if self.config.lud16:
|
||||||
|
content["lud16"] = self.config.lud16
|
||||||
event = NostrEvent(
|
event = NostrEvent(
|
||||||
pubkey=pubkey,
|
pubkey=pubkey,
|
||||||
created_at=round(time.time()),
|
created_at=round(time.time()),
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,7 @@ async def update_merchant_to_nostr(
|
||||||
stall.event_id = event.id
|
stall.event_id = event.id
|
||||||
stall.event_created_at = event.created_at
|
stall.event_created_at = event.created_at
|
||||||
await update_stall(merchant.id, stall)
|
await update_stall(merchant.id, stall)
|
||||||
if delete_merchant:
|
# Always publish merchant profile (kind 0)
|
||||||
# merchant profile updates not supported yet
|
|
||||||
event = await sign_and_send_to_nostr(merchant, merchant, delete_merchant)
|
event = await sign_and_send_to_nostr(merchant, merchant, delete_merchant)
|
||||||
assert event
|
assert event
|
||||||
merchant.config.event_id = event.id
|
merchant.config.event_id = event.id
|
||||||
|
|
|
||||||
91
static/components/edit-profile-dialog.js
Normal file
91
static/components/edit-profile-dialog.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
window.app.component('edit-profile-dialog', {
|
||||||
|
name: 'edit-profile-dialog',
|
||||||
|
template: '#edit-profile-dialog',
|
||||||
|
delimiters: ['${', '}'],
|
||||||
|
props: ['model-value', 'merchant-id', 'merchant-config', 'adminkey'],
|
||||||
|
emits: ['update:model-value', 'profile-updated'],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
saving: false,
|
||||||
|
formData: {
|
||||||
|
name: '',
|
||||||
|
display_name: '',
|
||||||
|
about: '',
|
||||||
|
picture: '',
|
||||||
|
banner: '',
|
||||||
|
website: '',
|
||||||
|
nip05: '',
|
||||||
|
lud16: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('update:model-value', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveProfile: async function () {
|
||||||
|
this.saving = true
|
||||||
|
try {
|
||||||
|
const config = {
|
||||||
|
...this.merchantConfig,
|
||||||
|
name: this.formData.name || null,
|
||||||
|
display_name: this.formData.display_name || null,
|
||||||
|
about: this.formData.about || null,
|
||||||
|
picture: this.formData.picture || null,
|
||||||
|
banner: this.formData.banner || null,
|
||||||
|
website: this.formData.website || null,
|
||||||
|
nip05: this.formData.nip05 || null,
|
||||||
|
lud16: this.formData.lud16 || null
|
||||||
|
}
|
||||||
|
await LNbits.api.request(
|
||||||
|
'PATCH',
|
||||||
|
`/nostrmarket/api/v1/merchant/${this.merchantId}`,
|
||||||
|
this.adminkey,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
// Publish to Nostr
|
||||||
|
await LNbits.api.request(
|
||||||
|
'PUT',
|
||||||
|
`/nostrmarket/api/v1/merchant/${this.merchantId}/nostr`,
|
||||||
|
this.adminkey
|
||||||
|
)
|
||||||
|
this.show = false
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Profile saved and published to Nostr!'
|
||||||
|
})
|
||||||
|
this.$emit('profile-updated')
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
} finally {
|
||||||
|
this.saving = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadFormData: function () {
|
||||||
|
if (this.merchantConfig) {
|
||||||
|
this.formData.name = this.merchantConfig.name || ''
|
||||||
|
this.formData.display_name = this.merchantConfig.display_name || ''
|
||||||
|
this.formData.about = this.merchantConfig.about || ''
|
||||||
|
this.formData.picture = this.merchantConfig.picture || ''
|
||||||
|
this.formData.banner = this.merchantConfig.banner || ''
|
||||||
|
this.formData.website = this.merchantConfig.website || ''
|
||||||
|
this.formData.nip05 = this.merchantConfig.nip05 || ''
|
||||||
|
this.formData.lud16 = this.merchantConfig.lud16 || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.loadFormData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
window.app.component('key-pair', {
|
|
||||||
name: 'key-pair',
|
|
||||||
template: '#key-pair',
|
|
||||||
delimiters: ['${', '}'],
|
|
||||||
props: ['public-key', 'private-key'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
showPrivateKey: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copyText: function (text, message, position) {
|
|
||||||
var notify = this.$q.notify
|
|
||||||
Quasar.copyToClipboard(text).then(function () {
|
|
||||||
notify({
|
|
||||||
message: message || 'Copied to clipboard!',
|
|
||||||
position: position || 'bottom'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -10,14 +10,46 @@ window.app.component('merchant-tab', {
|
||||||
'merchant-active',
|
'merchant-active',
|
||||||
'public-key',
|
'public-key',
|
||||||
'private-key',
|
'private-key',
|
||||||
'is-admin'
|
'is-admin',
|
||||||
|
'merchant-config'
|
||||||
],
|
],
|
||||||
|
emits: [
|
||||||
|
'toggle-show-keys',
|
||||||
|
'hide-keys',
|
||||||
|
'merchant-deleted',
|
||||||
|
'toggle-merchant-state',
|
||||||
|
'restart-nostr-connection',
|
||||||
|
'profile-updated',
|
||||||
|
'import-key',
|
||||||
|
'generate-key'
|
||||||
|
],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
showEditProfileDialog: false,
|
||||||
|
showKeysDialog: false
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
marketClientUrl: function () {
|
marketClientUrl: function () {
|
||||||
return '/nostrmarket/market'
|
return '/nostrmarket/market'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
publishProfile: async function () {
|
||||||
|
try {
|
||||||
|
await LNbits.api.request(
|
||||||
|
'PUT',
|
||||||
|
`/nostrmarket/api/v1/merchant/${this.merchantId}/nostr`,
|
||||||
|
this.adminkey
|
||||||
|
)
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Profile published to Nostr!'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
toggleShowKeys: function () {
|
toggleShowKeys: function () {
|
||||||
this.$emit('toggle-show-keys')
|
this.$emit('toggle-show-keys')
|
||||||
},
|
},
|
||||||
|
|
@ -27,11 +59,40 @@ window.app.component('merchant-tab', {
|
||||||
handleMerchantDeleted: function () {
|
handleMerchantDeleted: function () {
|
||||||
this.$emit('merchant-deleted')
|
this.$emit('merchant-deleted')
|
||||||
},
|
},
|
||||||
|
removeMerchant: function () {
|
||||||
|
const name =
|
||||||
|
this.merchantConfig?.display_name ||
|
||||||
|
this.merchantConfig?.name ||
|
||||||
|
'this merchant'
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog(
|
||||||
|
`Are you sure you want to remove "${name}"? This will delete all associated data (stalls, products, orders, messages).`
|
||||||
|
)
|
||||||
|
.onOk(async () => {
|
||||||
|
try {
|
||||||
|
await LNbits.api.request(
|
||||||
|
'DELETE',
|
||||||
|
`/nostrmarket/api/v1/merchant/${this.merchantId}`,
|
||||||
|
this.adminkey
|
||||||
|
)
|
||||||
|
this.$emit('merchant-deleted')
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Merchant removed'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
toggleMerchantState: function () {
|
toggleMerchantState: function () {
|
||||||
this.$emit('toggle-merchant-state')
|
this.$emit('toggle-merchant-state')
|
||||||
},
|
},
|
||||||
restartNostrConnection: function () {
|
restartNostrConnection: function () {
|
||||||
this.$emit('restart-nostr-connection')
|
this.$emit('restart-nostr-connection')
|
||||||
|
},
|
||||||
|
handleImageError: function (e) {
|
||||||
|
e.target.style.display = 'none'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
56
static/components/nostr-keys-dialog.js
Normal file
56
static/components/nostr-keys-dialog.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
window.app.component('nostr-keys-dialog', {
|
||||||
|
name: 'nostr-keys-dialog',
|
||||||
|
template: '#nostr-keys-dialog',
|
||||||
|
delimiters: ['${', '}'],
|
||||||
|
props: ['public-key', 'private-key', 'model-value'],
|
||||||
|
emits: ['update:model-value'],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
showNsec: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('update:model-value', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
npub: function () {
|
||||||
|
if (!this.publicKey) return ''
|
||||||
|
try {
|
||||||
|
return window.NostrTools.nip19.npubEncode(this.publicKey)
|
||||||
|
} catch (e) {
|
||||||
|
return this.publicKey
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nsec: function () {
|
||||||
|
if (!this.privateKey) return ''
|
||||||
|
try {
|
||||||
|
return window.NostrTools.nip19.nsecEncode(this.privateKey)
|
||||||
|
} catch (e) {
|
||||||
|
return this.privateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copyText: function (text, message) {
|
||||||
|
var notify = this.$q.notify
|
||||||
|
Quasar.copyToClipboard(text).then(function () {
|
||||||
|
notify({
|
||||||
|
message: message || 'Copied to clipboard!',
|
||||||
|
position: 'bottom'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(newVal) {
|
||||||
|
if (!newVal) {
|
||||||
|
this.showNsec = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -19,6 +19,13 @@ window.app = Vue.createApp({
|
||||||
privateKey: null
|
privateKey: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
generateKeyDialog: {
|
||||||
|
show: false,
|
||||||
|
privateKey: null,
|
||||||
|
nsec: null,
|
||||||
|
npub: null,
|
||||||
|
showNsec: false
|
||||||
|
},
|
||||||
wsConnection: null,
|
wsConnection: null,
|
||||||
nostrStatus: {
|
nostrStatus: {
|
||||||
connected: false,
|
connected: false,
|
||||||
|
|
@ -42,9 +49,18 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
generateKeys: async function () {
|
generateKeys: function () {
|
||||||
const privateKey = nostr.generatePrivateKey()
|
const privateKey = nostr.generatePrivateKey()
|
||||||
await this.createMerchant(privateKey)
|
const publicKey = nostr.getPublicKey(privateKey)
|
||||||
|
this.generateKeyDialog.privateKey = privateKey
|
||||||
|
this.generateKeyDialog.nsec = nostr.nip19.nsecEncode(privateKey)
|
||||||
|
this.generateKeyDialog.npub = nostr.nip19.npubEncode(publicKey)
|
||||||
|
this.generateKeyDialog.showNsec = false
|
||||||
|
this.generateKeyDialog.show = true
|
||||||
|
},
|
||||||
|
confirmGenerateKey: async function () {
|
||||||
|
this.generateKeyDialog.show = false
|
||||||
|
await this.createMerchant(this.generateKeyDialog.privateKey)
|
||||||
},
|
},
|
||||||
importKeys: async function () {
|
importKeys: async function () {
|
||||||
this.importKeyDialog.show = false
|
this.importKeyDialog.show = false
|
||||||
|
|
@ -56,11 +72,21 @@ window.app = Vue.createApp({
|
||||||
if (privateKey.toLowerCase().startsWith('nsec')) {
|
if (privateKey.toLowerCase().startsWith('nsec')) {
|
||||||
privateKey = nostr.nip19.decode(privateKey).data
|
privateKey = nostr.nip19.decode(privateKey).data
|
||||||
}
|
}
|
||||||
|
// Check if this key is already in use
|
||||||
|
const publicKey = nostr.getPublicKey(privateKey)
|
||||||
|
if (this.merchant?.public_key === publicKey) {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'This key is already your current profile'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: `${error}`
|
message: `${error}`
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
await this.createMerchant(privateKey)
|
await this.createMerchant(privateKey)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
68
templates/nostrmarket/components/edit-profile-dialog.html
Normal file
68
templates/nostrmarket/components/edit-profile-dialog.html
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<q-dialog v-model="show" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||||
|
<q-form @submit="saveProfile" class="q-gutter-md">
|
||||||
|
<div class="text-h6 q-mb-md">Edit Profile</div>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.name"
|
||||||
|
label="Name (username)"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.display_name"
|
||||||
|
label="Display Name"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.about"
|
||||||
|
label="About"
|
||||||
|
type="textarea"
|
||||||
|
rows="3"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.picture"
|
||||||
|
label="Profile Picture URL"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.banner"
|
||||||
|
label="Banner Image URL"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.website"
|
||||||
|
label="Website"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.nip05"
|
||||||
|
label="NIP-05 Identifier"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formData.lud16"
|
||||||
|
label="Lightning Address (lud16)"
|
||||||
|
></q-input>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
:loading="saving"
|
||||||
|
icon="publish"
|
||||||
|
>Save & Publish</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
<div>
|
|
||||||
<q-separator></q-separator>
|
|
||||||
|
|
||||||
<!-- Header with toggle -->
|
|
||||||
<div class="row items-center justify-between q-mt-md q-px-md">
|
|
||||||
<div class="text-subtitle2">Keys</div>
|
|
||||||
<q-toggle
|
|
||||||
v-model="showPrivateKey"
|
|
||||||
color="primary"
|
|
||||||
label="Show Private Key"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- QR Codes Container -->
|
|
||||||
<div class="row q-col-gutter-md q-pa-md">
|
|
||||||
<!-- Public Key QR -->
|
|
||||||
<div class="col-12" :class="showPrivateKey ? 'col-sm-6' : ''">
|
|
||||||
<q-card flat bordered>
|
|
||||||
<q-card-section class="text-center">
|
|
||||||
<div class="text-subtitle2 q-mb-sm">Public Key</div>
|
|
||||||
<div
|
|
||||||
class="cursor-pointer q-mx-auto"
|
|
||||||
style="max-width: 200px"
|
|
||||||
@click="copyText(publicKey)"
|
|
||||||
>
|
|
||||||
<q-responsive :ratio="1">
|
|
||||||
<lnbits-qrcode
|
|
||||||
:value="publicKey"
|
|
||||||
:options="{width: 200}"
|
|
||||||
:show-buttons="false"
|
|
||||||
class="rounded-borders"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
</div>
|
|
||||||
<div class="q-mt-md text-caption text-mono" style="padding: 0 16px">
|
|
||||||
<span v-text="publicKey.substring(0, 8)"></span>...<span
|
|
||||||
v-text="publicKey.substring(publicKey.length - 8)"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="sm"
|
|
||||||
icon="content_copy"
|
|
||||||
label="Click to copy"
|
|
||||||
@click="copyText(publicKey)"
|
|
||||||
class="q-mt-xs"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Private Key QR (conditional) -->
|
|
||||||
<div v-if="showPrivateKey" class="col-12 col-sm-6">
|
|
||||||
<q-card flat bordered>
|
|
||||||
<q-card-section class="text-center">
|
|
||||||
<div class="text-subtitle2 q-mb-sm text-warning">
|
|
||||||
<q-icon name="warning"></q-icon>
|
|
||||||
Private Key (Keep Secret!)
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="cursor-pointer q-mx-auto"
|
|
||||||
style="max-width: 200px"
|
|
||||||
@click="copyText(privateKey)"
|
|
||||||
>
|
|
||||||
<q-responsive :ratio="1">
|
|
||||||
<lnbits-qrcode
|
|
||||||
:value="privateKey"
|
|
||||||
:options="{width: 200}"
|
|
||||||
:show-buttons="false"
|
|
||||||
class="rounded-borders"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
</div>
|
|
||||||
<div class="q-mt-md text-caption text-mono" style="padding: 0 16px">
|
|
||||||
<span v-text="privateKey.substring(0, 8)"></span>...<span
|
|
||||||
v-text="privateKey.substring(privateKey.length - 8)"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="sm"
|
|
||||||
icon="content_copy"
|
|
||||||
label="Click to copy"
|
|
||||||
@click="copyText(privateKey)"
|
|
||||||
class="q-mt-xs"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,56 +1,277 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-8">
|
||||||
<div class="row items-center q-col-gutter-sm q-mb-md">
|
<q-card v-if="publicKey" flat bordered>
|
||||||
<div class="col-12 col-sm-auto">
|
<q-card-section>
|
||||||
<merchant-details
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
:merchant-id="merchantId"
|
|
||||||
:inkey="inkey"
|
|
||||||
:adminkey="adminkey"
|
|
||||||
:show-keys="showKeys"
|
|
||||||
@toggle-show-keys="toggleShowKeys"
|
|
||||||
@merchant-deleted="handleMerchantDeleted"
|
|
||||||
></merchant-details>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-auto q-mx-sm">
|
|
||||||
<div class="row items-center no-wrap">
|
|
||||||
<q-toggle
|
|
||||||
:model-value="merchantActive"
|
|
||||||
@update:model-value="toggleMerchantState()"
|
|
||||||
size="md"
|
|
||||||
checked-icon="check"
|
|
||||||
color="primary"
|
|
||||||
unchecked-icon="clear"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="q-ml-sm"
|
|
||||||
v-text="merchantActive ? 'Accepting Orders': 'Orders Paused'"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="showKeys" class="q-mt-md">
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
<span class="text-subtitle1">Merchant Profile</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mb-md q-gutter-sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
|
||||||
color="grey"
|
|
||||||
outline
|
outline
|
||||||
@click="hideKeys"
|
color="primary"
|
||||||
class="float-left"
|
@click="showEditProfileDialog = true"
|
||||||
>Hide Keys</q-btn
|
icon="edit"
|
||||||
|
>Edit</q-btn
|
||||||
>
|
>
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
icon="qr_code"
|
||||||
|
@click="showKeysDialog = true"
|
||||||
|
>
|
||||||
|
<q-tooltip>Show Keys</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn-dropdown
|
||||||
|
split
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
icon="swap_horiz"
|
||||||
|
label="Switch"
|
||||||
|
>
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header>Saved Profiles</q-item-label>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="check" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
><span
|
||||||
|
v-text="merchantConfig?.display_name || merchantConfig?.name || 'Current Profile'"
|
||||||
|
></span
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
class="text-mono"
|
||||||
|
style="font-size: 10px"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-text="publicKey ? publicKey.slice(0, 16) + '...' : ''"
|
||||||
|
></span>
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
icon="delete"
|
||||||
|
color="negative"
|
||||||
|
size="sm"
|
||||||
|
@click.stop="removeMerchant"
|
||||||
|
>
|
||||||
|
<q-tooltip>Remove profile</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-item clickable v-close-popup @click="$emit('import-key')">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="vpn_key" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Import Existing Key</q-item-label>
|
||||||
|
<q-item-label caption>Use an existing nsec</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="$emit('generate-key')">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="add" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Generate New Key</q-item-label>
|
||||||
|
<q-item-label caption>Create a fresh nsec</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
<q-btn-dropdown
|
||||||
|
split
|
||||||
|
outline
|
||||||
|
:color="merchantActive ? 'positive' : 'negative'"
|
||||||
|
:icon="merchantActive ? 'shopping_cart' : 'pause_circle'"
|
||||||
|
:label="merchantActive ? 'Orders On' : 'Orders Off'"
|
||||||
|
@click="toggleMerchantState"
|
||||||
|
>
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon
|
||||||
|
:name="merchantActive ? 'check_circle' : 'pause_circle'"
|
||||||
|
:color="merchantActive ? 'positive' : 'negative'"
|
||||||
|
></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label v-if="merchantActive"
|
||||||
|
>Accepting Orders</q-item-label
|
||||||
|
>
|
||||||
|
<q-item-label v-else>Orders Paused</q-item-label>
|
||||||
|
<q-item-label caption v-if="merchantActive">
|
||||||
|
New orders will be processed
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label caption v-else>
|
||||||
|
New orders will be ignored
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-item clickable v-close-popup @click="toggleMerchantState">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon
|
||||||
|
:name="merchantActive ? 'pause_circle' : 'play_circle'"
|
||||||
|
:color="merchantActive ? 'negative' : 'positive'"
|
||||||
|
></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label v-if="merchantActive"
|
||||||
|
>Pause Orders</q-item-label
|
||||||
|
>
|
||||||
|
<q-item-label v-else>Resume Orders</q-item-label>
|
||||||
|
<q-item-label caption v-if="merchantActive">
|
||||||
|
Stop accepting new orders
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label caption v-else>
|
||||||
|
Start accepting new orders
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- Banner Section -->
|
||||||
|
<div class="q-px-md">
|
||||||
|
<div
|
||||||
|
v-if="merchantConfig && merchantConfig.banner"
|
||||||
|
class="banner-container rounded-borders"
|
||||||
|
:style="{
|
||||||
|
height: '120px',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundImage: 'url(' + merchantConfig.banner + ')'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="banner-placeholder bg-grey-3 text-center rounded-borders"
|
||||||
|
style="height: 120px"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Section -->
|
||||||
|
<q-card-section class="q-pt-none q-ml-md" style="margin-top: -50px">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<!-- Profile Image -->
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-avatar size="100px" color="dark" class="profile-avatar">
|
||||||
|
<img
|
||||||
|
v-if="merchantConfig && merchantConfig.picture"
|
||||||
|
:src="merchantConfig.picture"
|
||||||
|
@error="handleImageError"
|
||||||
|
style="object-fit: cover"
|
||||||
|
/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
name="person"
|
||||||
|
size="60px"
|
||||||
|
color="grey-5"
|
||||||
|
></q-icon>
|
||||||
|
</q-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name, About and NIP-05 -->
|
||||||
|
<div class="col q-pl-md" style="padding-top: 55px">
|
||||||
|
<div class="row items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<key-pair
|
<div
|
||||||
|
class="text-h6"
|
||||||
|
v-if="merchantConfig && merchantConfig.display_name"
|
||||||
|
>
|
||||||
|
<span v-text="merchantConfig.display_name"></span>
|
||||||
|
</div>
|
||||||
|
<div class="text-caption text-grey" v-else>
|
||||||
|
(No display name set)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- TODO: Unhide when following/followers is implemented -->
|
||||||
|
<div v-if="false" class="col-auto q-mr-sm">
|
||||||
|
<div class="row q-gutter-md">
|
||||||
|
<div class="text-caption text-grey-6">
|
||||||
|
<span class="text-weight-bold">0</span> Following
|
||||||
|
<q-tooltip>Not implemented yet</q-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="text-caption text-grey-6">
|
||||||
|
<span class="text-weight-bold">0</span> Followers
|
||||||
|
<q-tooltip>Not implemented yet</q-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-body2 text-grey-8 q-mt-xs"
|
||||||
|
v-if="merchantConfig && merchantConfig.about"
|
||||||
|
style="max-width: 400px"
|
||||||
|
>
|
||||||
|
<span v-text="merchantConfig.about"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mt-xs q-gutter-sm">
|
||||||
|
<div
|
||||||
|
class="text-caption text-grey-5"
|
||||||
|
v-if="merchantConfig && merchantConfig.nip05"
|
||||||
|
>
|
||||||
|
<q-icon name="verified" color="primary" size="14px"></q-icon>
|
||||||
|
<span v-text="merchantConfig.nip05" class="q-ml-xs"></span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-caption text-grey-5"
|
||||||
|
v-if="merchantConfig && merchantConfig.lud16"
|
||||||
|
>
|
||||||
|
<q-icon name="bolt" color="warning" size="14px"></q-icon>
|
||||||
|
<span v-text="merchantConfig.lud16" class="q-ml-xs"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<div class="row items-center q-px-md">
|
||||||
|
<q-separator class="col"></q-separator>
|
||||||
|
<q-btn fab icon="add" color="primary" class="q-ml-md" disable>
|
||||||
|
<q-tooltip>New Post (Coming soon)</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feed Section (Not Implemented) -->
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-center q-pa-lg" style="opacity: 0.5">
|
||||||
|
<q-icon name="chat" size="48px" class="q-mb-sm text-grey"></q-icon>
|
||||||
|
<div class="text-subtitle2 text-grey">Coming Soon</div>
|
||||||
|
<div class="text-caption text-grey">
|
||||||
|
Merchant posts will appear here
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Profile Dialog -->
|
||||||
|
<edit-profile-dialog
|
||||||
|
v-model="showEditProfileDialog"
|
||||||
|
:merchant-id="merchantId"
|
||||||
|
:merchant-config="merchantConfig"
|
||||||
|
:adminkey="adminkey"
|
||||||
|
@profile-updated="$emit('profile-updated')"
|
||||||
|
></edit-profile-dialog>
|
||||||
|
|
||||||
|
<!-- Nostr Keys Dialog -->
|
||||||
|
<nostr-keys-dialog
|
||||||
|
v-model="showKeysDialog"
|
||||||
:public-key="publicKey"
|
:public-key="publicKey"
|
||||||
:private-key="privateKey"
|
:private-key="privateKey"
|
||||||
></key-pair>
|
></nostr-keys-dialog>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
75
templates/nostrmarket/components/nostr-keys-dialog.html
Normal file
75
templates/nostrmarket/components/nostr-keys-dialog.html
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<q-dialog v-model="show">
|
||||||
|
<q-card style="min-width: 400px; max-width: 450px">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6">Nostr Keys</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<!-- QR Code for npub only -->
|
||||||
|
<div class="q-mx-auto q-mb-md text-center" style="max-width: 200px">
|
||||||
|
<lnbits-qrcode
|
||||||
|
:value="npub"
|
||||||
|
:options="{ width: 200 }"
|
||||||
|
:show-buttons="false"
|
||||||
|
class="rounded-borders"
|
||||||
|
></lnbits-qrcode>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Public Key (npub) -->
|
||||||
|
<div class="text-subtitle2 q-mb-xs">
|
||||||
|
<q-icon name="public" class="q-mr-xs"></q-icon>
|
||||||
|
Public Key (npub)
|
||||||
|
</div>
|
||||||
|
<q-input :model-value="npub" readonly dense outlined class="q-mb-md">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(npub, 'npub copied!')"
|
||||||
|
>
|
||||||
|
<q-tooltip>Copy npub</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<!-- Private Key (nsec) -->
|
||||||
|
<div class="text-subtitle2 q-mb-xs text-warning">
|
||||||
|
<q-icon name="warning" class="q-mr-xs"></q-icon>
|
||||||
|
Private Key (nsec)
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
:model-value="showNsec ? nsec : '••••••••••••••••••••••••••••••••••••••••••••••'"
|
||||||
|
readonly
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
class="q-mb-xs"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
:icon="showNsec ? 'visibility_off' : 'visibility'"
|
||||||
|
@click="showNsec = !showNsec"
|
||||||
|
>
|
||||||
|
<q-tooltip v-text="showNsec ? 'Hide' : 'Show'"></q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(nsec, 'nsec copied! Keep it secret!')"
|
||||||
|
>
|
||||||
|
<q-tooltip>Copy nsec</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<div class="text-caption text-negative">
|
||||||
|
<q-icon name="error" size="14px"></q-icon>
|
||||||
|
Never share your private key!
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="q-pa-md">
|
||||||
|
<q-btn flat label="CLOSE" color="grey" v-close-popup></q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
@ -53,11 +53,15 @@
|
||||||
:public-key="merchant.public_key"
|
:public-key="merchant.public_key"
|
||||||
:private-key="merchant.private_key"
|
:private-key="merchant.private_key"
|
||||||
:is-admin="g.user.admin"
|
:is-admin="g.user.admin"
|
||||||
|
:merchant-config="merchant.config"
|
||||||
@toggle-show-keys="toggleShowKeys"
|
@toggle-show-keys="toggleShowKeys"
|
||||||
@hide-keys="showKeys = false"
|
@hide-keys="showKeys = false"
|
||||||
@merchant-deleted="handleMerchantDeleted"
|
@merchant-deleted="handleMerchantDeleted"
|
||||||
@toggle-merchant-state="toggleMerchantState"
|
@toggle-merchant-state="toggleMerchantState"
|
||||||
@restart-nostr-connection="restartNostrConnection"
|
@restart-nostr-connection="restartNostrConnection"
|
||||||
|
@import-key="showImportKeysDialog"
|
||||||
|
@generate-key="generateKeys"
|
||||||
|
@profile-updated="getMerchant"
|
||||||
></merchant-tab>
|
></merchant-tab>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
|
|
||||||
|
|
@ -410,6 +414,63 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Generate Key Dialog -->
|
||||||
|
<q-dialog v-model="generateKeyDialog.show" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||||
|
<div class="text-h6 q-mb-md">Generate New Key</div>
|
||||||
|
<div class="q-mb-md">
|
||||||
|
<div class="text-subtitle2 q-mb-xs">Public Key (npub)</div>
|
||||||
|
<q-input :model-value="generateKeyDialog.npub" readonly dense outlined>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(generateKeyDialog.npub, 'npub copied!')"
|
||||||
|
></q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="q-mb-md">
|
||||||
|
<div class="text-subtitle2 q-mb-xs text-warning">
|
||||||
|
<q-icon name="warning" size="xs"></q-icon>
|
||||||
|
Private Key (nsec)
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
:model-value="generateKeyDialog.showNsec ? generateKeyDialog.nsec : '••••••••••••••••••••••••••••••••••••••••••••••'"
|
||||||
|
readonly
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
:icon="generateKeyDialog.showNsec ? 'visibility_off' : 'visibility'"
|
||||||
|
@click="generateKeyDialog.showNsec = !generateKeyDialog.showNsec"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(generateKeyDialog.nsec, 'nsec copied! Keep it safe!')"
|
||||||
|
></q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<div class="text-caption text-negative q-mt-xs">
|
||||||
|
<q-icon name="error" size="xs"></q-icon>
|
||||||
|
Never share your private key!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn unelevated color="primary" @click="confirmGenerateKey"
|
||||||
|
>Create Merchant</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock%}{% block scripts %} {{ window_vars(user) }}
|
{% endblock%}{% block scripts %} {{ window_vars(user) }}
|
||||||
|
|
||||||
|
|
@ -427,10 +488,23 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
border: 3px solid var(--q-dark-page);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar .q-avatar__content {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template id="key-pair"
|
<template id="nostr-keys-dialog"
|
||||||
>{% include("nostrmarket/components/key-pair.html") %}</template
|
>{% include("nostrmarket/components/nostr-keys-dialog.html") %}</template
|
||||||
|
>
|
||||||
|
<template id="edit-profile-dialog"
|
||||||
|
>{% include("nostrmarket/components/edit-profile-dialog.html") %}</template
|
||||||
>
|
>
|
||||||
<template id="shipping-zones"
|
<template id="shipping-zones"
|
||||||
>{% include("nostrmarket/components/shipping-zones.html") %}</template
|
>{% include("nostrmarket/components/shipping-zones.html") %}</template
|
||||||
|
|
@ -464,7 +538,8 @@
|
||||||
<script src="{{ url_for('nostrmarket_static', path='js/utils.js') }}"></script>
|
<script src="{{ url_for('nostrmarket_static', path='js/utils.js') }}"></script>
|
||||||
<script src="{{ url_for('nostrmarket_static', path='js/index.js') }}"></script>
|
<script src="{{ url_for('nostrmarket_static', path='js/index.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ static_url_for('nostrmarket/static', 'components/key-pair.js') }}"></script>
|
<script src="{{ static_url_for('nostrmarket/static', 'components/nostr-keys-dialog.js') }}"></script>
|
||||||
|
<script src="{{ static_url_for('nostrmarket/static', 'components/edit-profile-dialog.js') }}"></script>
|
||||||
<script src="{{ static_url_for('nostrmarket/static', 'components/shipping-zones.js') }}"></script>
|
<script src="{{ static_url_for('nostrmarket/static', 'components/shipping-zones.js') }}"></script>
|
||||||
<script src="{{ static_url_for('nostrmarket/static', 'components/stall-details.js') }}"></script>
|
<script src="{{ static_url_for('nostrmarket/static', 'components/stall-details.js') }}"></script>
|
||||||
<script src="{{ static_url_for('nostrmarket/static', 'components/stall-list.js') }}"></script>
|
<script src="{{ static_url_for('nostrmarket/static', 'components/stall-list.js') }}"></script>
|
||||||
|
|
|
||||||
33
views_api.py
33
views_api.py
|
|
@ -62,6 +62,7 @@ from .models import (
|
||||||
DirectMessage,
|
DirectMessage,
|
||||||
DirectMessageType,
|
DirectMessageType,
|
||||||
Merchant,
|
Merchant,
|
||||||
|
MerchantConfig,
|
||||||
Order,
|
Order,
|
||||||
OrderReissue,
|
OrderReissue,
|
||||||
OrderStatusUpdate,
|
OrderStatusUpdate,
|
||||||
|
|
@ -97,9 +98,6 @@ async def api_create_merchant(
|
||||||
merchant = await get_merchant_by_pubkey(data.public_key)
|
merchant = await get_merchant_by_pubkey(data.public_key)
|
||||||
assert merchant is None, "A merchant already uses this public key"
|
assert merchant is None, "A merchant already uses this public key"
|
||||||
|
|
||||||
merchant = await get_merchant_for_user(wallet.wallet.user)
|
|
||||||
assert merchant is None, "A merchant already exists for this user"
|
|
||||||
|
|
||||||
merchant = await create_merchant(wallet.wallet.user, data)
|
merchant = await create_merchant(wallet.wallet.user, data)
|
||||||
|
|
||||||
await create_zone(
|
await create_zone(
|
||||||
|
|
@ -192,6 +190,35 @@ async def api_delete_merchant(
|
||||||
await subscribe_to_all_merchants()
|
await subscribe_to_all_merchants()
|
||||||
|
|
||||||
|
|
||||||
|
@nostrmarket_ext.patch("/api/v1/merchant/{merchant_id}")
|
||||||
|
async def api_update_merchant(
|
||||||
|
merchant_id: str,
|
||||||
|
config: MerchantConfig,
|
||||||
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
merchant = await get_merchant_for_user(wallet.wallet.user)
|
||||||
|
assert merchant, "Merchant cannot be found"
|
||||||
|
assert merchant.id == merchant_id, "Wrong merchant ID"
|
||||||
|
|
||||||
|
updated_merchant = await update_merchant(
|
||||||
|
wallet.wallet.user, merchant_id, config
|
||||||
|
)
|
||||||
|
return updated_merchant
|
||||||
|
|
||||||
|
except AssertionError as ex:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=str(ex),
|
||||||
|
) from ex
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(ex)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Cannot update merchant",
|
||||||
|
) from ex
|
||||||
|
|
||||||
|
|
||||||
@nostrmarket_ext.put("/api/v1/merchant/{merchant_id}/nostr")
|
@nostrmarket_ext.put("/api/v1/merchant/{merchant_id}/nostr")
|
||||||
async def api_republish_merchant(
|
async def api_republish_merchant(
|
||||||
merchant_id: str,
|
merchant_id: str,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue