feat: restructure UI with tab-based navigation (#119)
Reorganizes the merchant dashboard into a tab-based layout: - Tabs: Merchant | Shipping | Stalls | Products | Messages | Orders - Publish dropdown with NIP-15 options (NIP-99 disabled/coming soon) - Consistent UI patterns across all tabs: - Search/filter/buttons aligned right - Actions column on right side of tables - Equal-width tabs - Stalls tab: popup edit dialog, navigation to Products/Orders - Products tab: filter icon dropdown for stall filtering - Modular component structure for each tab section - Fixed product API calls to use correct endpoints Closes #119 🤖 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
17d13dbe6b
commit
71f458b9b9
10 changed files with 1605 additions and 384 deletions
|
|
@ -2,7 +2,7 @@ window.app.component('stall-list', {
|
|||
name: 'stall-list',
|
||||
template: '#stall-list',
|
||||
delimiters: ['${', '}'],
|
||||
props: [`adminkey`, 'inkey', 'wallet-options'],
|
||||
props: ['adminkey', 'inkey', 'wallet-options'],
|
||||
data: function () {
|
||||
return {
|
||||
filter: '',
|
||||
|
|
@ -20,39 +20,25 @@ window.app.component('stall-list', {
|
|||
shippingZones: []
|
||||
}
|
||||
},
|
||||
editDialog: {
|
||||
show: false,
|
||||
data: {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
wallet: null,
|
||||
currency: 'sat',
|
||||
shippingZones: []
|
||||
}
|
||||
},
|
||||
zoneOptions: [],
|
||||
stallsTable: {
|
||||
columns: [
|
||||
{
|
||||
name: '',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: 'Name',
|
||||
field: 'id'
|
||||
},
|
||||
{
|
||||
name: 'currency',
|
||||
align: 'left',
|
||||
label: 'Currency',
|
||||
field: 'currency'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
label: 'Description',
|
||||
field: 'description'
|
||||
},
|
||||
{
|
||||
name: 'shippingZones',
|
||||
align: 'left',
|
||||
label: 'Shipping Zones',
|
||||
field: 'shippingZones'
|
||||
}
|
||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
||||
{name: 'currency', align: 'left', label: 'Currency', field: 'currency'},
|
||||
{name: 'description', align: 'left', label: 'Description', field: row => row.config?.description || ''},
|
||||
{name: 'shippingZones', align: 'left', label: 'Shipping Zones', field: row => row.shipping_zones?.map(z => z.name).join(', ') || ''},
|
||||
{name: 'actions', align: 'right', label: 'Actions', field: ''}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
|
|
@ -65,6 +51,11 @@ window.app.component('stall-list', {
|
|||
return this.zoneOptions.filter(
|
||||
z => z.currency === this.stallDialog.data.currency
|
||||
)
|
||||
},
|
||||
editFilteredZoneOptions: function () {
|
||||
return this.zoneOptions.filter(
|
||||
z => z.currency === this.editDialog.data.currency
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -94,7 +85,6 @@ window.app.component('stall-list', {
|
|||
stall
|
||||
)
|
||||
this.stallDialog.show = false
|
||||
data.expanded = false
|
||||
this.stalls.unshift(data)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
|
|
@ -114,7 +104,6 @@ window.app.component('stall-list', {
|
|||
stallData
|
||||
)
|
||||
this.stallDialog.show = false
|
||||
data.expanded = false
|
||||
this.stalls.unshift(data)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
|
|
@ -124,44 +113,66 @@ window.app.component('stall-list', {
|
|||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
deleteStall: async function (pendingStall) {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
`
|
||||
Are you sure you want to delete this pending stall '${pendingStall.name}'?
|
||||
`
|
||||
)
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
'/nostrmarket/api/v1/stall/' + pendingStall.id,
|
||||
this.adminkey
|
||||
)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Pending Stall Deleted',
|
||||
timeout: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
getCurrencies: async function () {
|
||||
updateStall: async function () {
|
||||
try {
|
||||
const stallData = {
|
||||
id: this.editDialog.data.id,
|
||||
name: this.editDialog.data.name,
|
||||
wallet: this.editDialog.data.wallet,
|
||||
currency: this.editDialog.data.currency,
|
||||
shipping_zones: this.editDialog.data.shippingZones,
|
||||
config: {
|
||||
description: this.editDialog.data.description
|
||||
}
|
||||
}
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1/currencies',
|
||||
this.inkey
|
||||
'PUT',
|
||||
`/nostrmarket/api/v1/stall/${stallData.id}`,
|
||||
this.adminkey,
|
||||
stallData
|
||||
)
|
||||
|
||||
return ['sat', ...data]
|
||||
this.editDialog.show = false
|
||||
const index = this.stalls.findIndex(s => s.id === data.id)
|
||||
if (index !== -1) {
|
||||
this.stalls.splice(index, 1, data)
|
||||
}
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Stall updated!'
|
||||
})
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
return []
|
||||
},
|
||||
deleteStall: async function (stall) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
'/nostrmarket/api/v1/stall/' + stall.id,
|
||||
this.adminkey
|
||||
)
|
||||
this.stalls = this.stalls.filter(s => s.id !== stall.id)
|
||||
this.pendingStalls = this.pendingStalls.filter(s => s.id !== stall.id)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Stall deleted'
|
||||
})
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
confirmDeleteStall: function (stall) {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
`Products and orders will be deleted also! Are you sure you want to delete stall "${stall.name}"?`
|
||||
)
|
||||
.onOk(async () => {
|
||||
await this.deleteStall(stall)
|
||||
})
|
||||
},
|
||||
getCurrencies: function () {
|
||||
const currencies = window.g.allowedCurrencies || []
|
||||
return ['sat', ...currencies]
|
||||
},
|
||||
getStalls: async function (pending = false) {
|
||||
try {
|
||||
|
|
@ -170,7 +181,7 @@ window.app.component('stall-list', {
|
|||
`/nostrmarket/api/v1/stall?pending=${pending}`,
|
||||
this.inkey
|
||||
)
|
||||
return data.map(s => ({...s, expanded: false}))
|
||||
return data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
|
|
@ -194,20 +205,8 @@ window.app.component('stall-list', {
|
|||
}
|
||||
return []
|
||||
},
|
||||
handleStallDeleted: function (stallId) {
|
||||
this.stalls = _.reject(this.stalls, function (obj) {
|
||||
return obj.id === stallId
|
||||
})
|
||||
},
|
||||
handleStallUpdated: function (stall) {
|
||||
const index = this.stalls.findIndex(r => r.id === stall.id)
|
||||
if (index !== -1) {
|
||||
stall.expanded = true
|
||||
this.stalls.splice(index, 1, stall)
|
||||
}
|
||||
},
|
||||
openCreateStallDialog: async function (stallData) {
|
||||
this.currencies = await this.getCurrencies()
|
||||
this.currencies = this.getCurrencies()
|
||||
this.zoneOptions = await this.getZones()
|
||||
if (!this.zoneOptions || !this.zoneOptions.length) {
|
||||
this.$q.notify({
|
||||
|
|
@ -225,6 +224,24 @@ window.app.component('stall-list', {
|
|||
}
|
||||
this.stallDialog.show = true
|
||||
},
|
||||
openEditStallDialog: async function (stall) {
|
||||
this.currencies = this.getCurrencies()
|
||||
this.zoneOptions = await this.getZones()
|
||||
this.editDialog.data = {
|
||||
id: stall.id,
|
||||
name: stall.name,
|
||||
description: stall.config?.description || '',
|
||||
wallet: stall.wallet,
|
||||
currency: stall.currency,
|
||||
shippingZones: (stall.shipping_zones || []).map(z => ({
|
||||
...z,
|
||||
label: z.name
|
||||
? `${z.name} (${z.countries.join(', ')})`
|
||||
: z.countries.join(', ')
|
||||
}))
|
||||
}
|
||||
this.editDialog.show = true
|
||||
},
|
||||
openSelectPendingStallDialog: async function () {
|
||||
this.stallDialog.showRestore = true
|
||||
this.pendingStalls = await this.getStalls(true)
|
||||
|
|
@ -246,8 +263,11 @@ window.app.component('stall-list', {
|
|||
}))
|
||||
})
|
||||
},
|
||||
customerSelectedForOrder: function (customerPubkey) {
|
||||
this.$emit('customer-selected-for-order', customerPubkey)
|
||||
goToProducts: function (stall) {
|
||||
this.$emit('go-to-products', stall.id)
|
||||
},
|
||||
goToOrders: function (stall) {
|
||||
this.$emit('go-to-orders', stall.id)
|
||||
},
|
||||
shortLabel(value = '') {
|
||||
if (value.length <= 64) return value
|
||||
|
|
@ -256,7 +276,7 @@ window.app.component('stall-list', {
|
|||
},
|
||||
created: async function () {
|
||||
this.stalls = await this.getStalls()
|
||||
this.currencies = await this.getCurrencies()
|
||||
this.currencies = this.getCurrencies()
|
||||
this.zoneOptions = await this.getZones()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue