feat: multi-profile support and UI improvements
- Remove backend restriction on one merchant per user - Add "Generate New Key" dialog with npub/nsec display - Add "Import Existing Key" option with duplicate check - Change "Save" to "Save & Publish" in edit profile dialog - Remove standalone Publish button (now part of Save) - Add trash icon to saved profile for removal - Show display_name in saved profiles dropdown - Hide nsec by default with eye toggle in generate dialog 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f466559b51
commit
7aec14854c
6 changed files with 113 additions and 40 deletions
|
|
@ -50,10 +50,16 @@ window.app.component('edit-profile-dialog', {
|
|||
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 updated!'
|
||||
message: 'Profile saved and published to Nostr!'
|
||||
})
|
||||
this.$emit('profile-updated')
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ window.app = Vue.createApp({
|
|||
privateKey: null
|
||||
}
|
||||
},
|
||||
generateKeyDialog: {
|
||||
show: false,
|
||||
privateKey: null,
|
||||
nsec: null,
|
||||
npub: null,
|
||||
showNsec: false
|
||||
},
|
||||
wsConnection: null,
|
||||
nostrStatus: {
|
||||
connected: false,
|
||||
|
|
@ -41,9 +48,18 @@ window.app = Vue.createApp({
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
generateKeys: async function () {
|
||||
generateKeys: function () {
|
||||
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 () {
|
||||
this.importKeyDialog.show = false
|
||||
|
|
@ -55,11 +71,21 @@ window.app = Vue.createApp({
|
|||
if (privateKey.toLowerCase().startsWith('nsec')) {
|
||||
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) {
|
||||
this.$q.notify({
|
||||
type: 'negative',
|
||||
message: `${error}`
|
||||
})
|
||||
return
|
||||
}
|
||||
await this.createMerchant(privateKey)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,8 +53,13 @@
|
|||
label="Lightning Address (lud16)"
|
||||
></q-input>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn unelevated color="primary" type="submit" :loading="saving"
|
||||
>Save</q-btn
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -24,13 +24,6 @@
|
|||
>
|
||||
<q-tooltip>Show Keys</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
outline
|
||||
color="primary"
|
||||
@click="publishProfile"
|
||||
icon="publish"
|
||||
>Publish</q-btn
|
||||
>
|
||||
<q-btn-dropdown
|
||||
split
|
||||
outline
|
||||
|
|
@ -40,14 +33,14 @@
|
|||
>
|
||||
<q-list>
|
||||
<q-item-label header>Saved Profiles</q-item-label>
|
||||
<q-item clickable v-close-popup>
|
||||
<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?.name || 'Current Profile'"
|
||||
v-text="merchantConfig?.display_name || merchantConfig?.name || 'Current Profile'"
|
||||
></span
|
||||
></q-item-label>
|
||||
<q-item-label
|
||||
|
|
@ -60,12 +53,18 @@
|
|||
></span>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item disable>
|
||||
<q-item-section>
|
||||
<q-item-label caption class="text-grey-6">
|
||||
Multi-profile support coming soon
|
||||
</q-item-label>
|
||||
<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>
|
||||
|
|
@ -87,23 +86,6 @@
|
|||
<q-item-label caption>Create a fresh nsec</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator></q-separator>
|
||||
<q-item clickable v-close-popup @click="removeMerchant">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="delete" color="negative"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-negative"
|
||||
>Remove
|
||||
<span
|
||||
v-text="merchantConfig?.display_name || merchantConfig?.name || 'Profile'"
|
||||
></span
|
||||
></q-item-label>
|
||||
<q-item-label caption
|
||||
>Remove this nPub from the DB</q-item-label
|
||||
>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<q-btn-dropdown
|
||||
|
|
|
|||
|
|
@ -408,6 +408,63 @@
|
|||
</q-card>
|
||||
</q-dialog>
|
||||
</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>
|
||||
{% endblock%}{% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@ async def api_create_merchant(
|
|||
merchant = await get_merchant_by_pubkey(data.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)
|
||||
|
||||
await create_zone(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue