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:
Ben Weeks 2025-12-21 19:06:28 +00:00
parent 17d13dbe6b
commit 71f458b9b9
10 changed files with 1605 additions and 384 deletions

View file

@ -0,0 +1,203 @@
window.app.component('shipping-zones-list', {
name: 'shipping-zones-list',
props: ['adminkey', 'inkey'],
template: '#shipping-zones-list',
delimiters: ['${', '}'],
data: function () {
return {
zones: [],
zoneDialog: {
showDialog: false,
data: {
id: null,
name: '',
countries: [],
cost: 0,
currency: 'sat'
}
},
currencies: [],
shippingZoneOptions: [
'Free (digital)',
'Flat rate',
'Worldwide',
'Europe',
'Australia',
'Austria',
'Belgium',
'Brazil',
'Canada',
'Denmark',
'Finland',
'France',
'Germany',
'Greece',
'Hong Kong',
'Hungary',
'Ireland',
'Indonesia',
'Israel',
'Italy',
'Japan',
'Kazakhstan',
'Korea',
'Luxembourg',
'Malaysia',
'Mexico',
'Netherlands',
'New Zealand',
'Norway',
'Poland',
'Portugal',
'Romania',
'Russia',
'Saudi Arabia',
'Singapore',
'Spain',
'Sweden',
'Switzerland',
'Thailand',
'Turkey',
'Ukraine',
'United Kingdom**',
'United States***',
'Vietnam',
'China'
],
zonesTable: {
columns: [
{
name: 'name',
align: 'left',
label: 'Name',
field: 'name'
},
{
name: 'countries',
align: 'left',
label: 'Countries',
field: 'countries'
},
{
name: 'currency',
align: 'left',
label: 'Currency',
field: 'currency'
},
{
name: 'cost',
align: 'left',
label: 'Cost',
field: 'cost'
},
{
name: 'actions',
align: 'right',
label: 'Actions',
field: ''
}
],
pagination: {
rowsPerPage: 10
}
}
}
},
methods: {
openZoneDialog: function (data) {
data = data || {
id: null,
name: '',
countries: [],
cost: 0,
currency: 'sat'
}
this.zoneDialog.data = {...data}
this.zoneDialog.showDialog = true
},
getZones: async function () {
try {
const {data} = await LNbits.api.request(
'GET',
'/nostrmarket/api/v1/zone',
this.inkey
)
this.zones = data
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
sendZoneFormData: async function () {
this.zoneDialog.showDialog = false
if (this.zoneDialog.data.id) {
await this.updateShippingZone(this.zoneDialog.data)
} else {
await this.createShippingZone(this.zoneDialog.data)
}
await this.getZones()
},
createShippingZone: async function (newZone) {
try {
await LNbits.api.request(
'POST',
'/nostrmarket/api/v1/zone',
this.adminkey,
newZone
)
this.$q.notify({
type: 'positive',
message: 'Zone created!'
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
updateShippingZone: async function (updatedZone) {
try {
await LNbits.api.request(
'PATCH',
`/nostrmarket/api/v1/zone/${updatedZone.id}`,
this.adminkey,
updatedZone
)
this.$q.notify({
type: 'positive',
message: 'Zone updated!'
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
confirmDeleteZone: function (zone) {
LNbits.utils
.confirmDialog(`Are you sure you want to delete zone "${zone.name}"?`)
.onOk(async () => {
await this.deleteShippingZone(zone.id)
})
},
deleteShippingZone: async function (zoneId) {
try {
await LNbits.api.request(
'DELETE',
`/nostrmarket/api/v1/zone/${zoneId}`,
this.adminkey
)
this.$q.notify({
type: 'positive',
message: 'Zone deleted!'
})
await this.getZones()
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
getCurrencies() {
const currencies = window.g.allowedCurrencies || []
this.currencies = ['sat', ...currencies]
}
},
created: async function () {
await this.getZones()
this.getCurrencies()
}
})