@stacksjs/defaults 0.75.233 → 0.75.237

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/ai/skills/stacks-auth/SKILL.md +9 -1
  2. package/ai/skills/stacks-dashboard/SKILL.md +2 -2
  3. package/ai/skills/stacks-database/SKILL.md +1 -1
  4. package/ai/skills/stacks-deploy/SKILL.md +3 -2
  5. package/ai/skills/stacks-migrations/SKILL.md +2 -2
  6. package/ai/skills/stacks-native/CATALOG.md +12 -12
  7. package/ai/skills/stacks-realtime/SKILL.md +16 -2
  8. package/ai/skills/stacks-router/SKILL.md +8 -0
  9. package/ai/skills/stacks-testing/SKILL.md +5 -0
  10. package/app/Actions/Monitoring/error-provider.ts +129 -12
  11. package/app/Models/Error.ts +21 -0
  12. package/ide/vscode/package.json +1 -1
  13. package/package.json +2 -2
  14. package/resources/components/Dashboard/Auth/AuthCard.stx +29 -0
  15. package/resources/components/Dashboard/Auth/ForgotPassword.stx +65 -79
  16. package/resources/components/Dashboard/Auth/Login.stx +73 -82
  17. package/resources/components/Dashboard/Auth/Register.stx +121 -135
  18. package/resources/components/Dashboard/Auth/ResetPassword.stx +88 -102
  19. package/resources/components/Dashboard/Buddy/BuddyAssistant.stx +11 -15
  20. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +8 -20
  21. package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +8 -20
  22. package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +8 -20
  23. package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +8 -20
  24. package/resources/components/Dashboard/Commerce/CommerceManufacturersDashboard.stx +13 -24
  25. package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +8 -20
  26. package/resources/components/Dashboard/Commerce/CommerceOverviewDashboard.stx +17 -21
  27. package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +7 -19
  28. package/resources/components/Dashboard/Commerce/CommercePosDashboard.stx +4 -16
  29. package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +8 -20
  30. package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +7 -18
  31. package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +13 -24
  32. package/resources/components/Dashboard/Commerce/CommerceUnitsDashboard.stx +13 -24
  33. package/resources/components/Dashboard/Commerce/CommerceVariantsDashboard.stx +7 -18
  34. package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +7 -13
  35. package/resources/components/Dashboard/Commerce/Delivery/DeliveryOverviewDashboard.stx +12 -16
  36. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +7 -13
  37. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveriesDashboard.stx +7 -13
  38. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +7 -13
  39. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsDashboard.stx +7 -13
  40. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +7 -13
  41. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +7 -13
  42. package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +8 -20
  43. package/resources/components/Dashboard/Commerce/PrintLogsDashboard.stx +7 -19
  44. package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +12 -16
  45. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +12 -16
  46. package/resources/components/Dashboard/Deployments/DeploymentList.stx +17 -21
  47. package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +6 -10
  48. package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +6 -10
  49. package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +9 -13
  50. package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +6 -10
  51. package/resources/components/Dashboard/Queue/QueueDashboard.stx +12 -16
  52. package/resources/components/Dashboard/Realtime/RealtimeDashboard.stx +6 -10
  53. package/resources/components/Dashboard/Releases/ReleaseDashboard.stx +6 -10
  54. package/resources/components/Dashboard/UI/MetricCards.stx +32 -0
  55. package/resources/components/Dashboard/UI/PageHeader.stx +25 -66
  56. package/resources/components/Dashboard/UI/Table.stx +79 -6
@@ -314,25 +314,13 @@ onMount(() => {
314
314
  </script>
315
315
 
316
316
  <div class="space-y-6">
317
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
318
- <div>
319
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Point of sale</h1>
320
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Create server-priced cash sales from native Product, Order, OrderItem, Payment, Customer, and TaxRate records.</p>
321
- </div>
322
- <Button variant="secondary" :loading="loading()" @click="loadPos()"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
323
- </header>
317
+ <PageHeader title="Point of sale" description="Create server-priced cash sales from native Product, Order, OrderItem, Payment, Customer, and TaxRate records.">
318
+ <Button variant="secondary" :loading="loading()" @click="loadPos()"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
319
+ </PageHeader>
324
320
 
325
321
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
326
322
 
327
- <section aria-label="Point of sale metrics" class="grid gap-3 grid-cols-2 xl:grid-cols-4">
328
- <template :for="item in metricCards()" :key="item.label">
329
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
330
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
331
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
332
- <p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p>
333
- </article>
334
- </template>
335
- </section>
323
+ <MetricCards label="Point of sale metrics" columns="grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
336
324
 
337
325
  <div class="grid gap-6 grid-cols-1 items-start lg:grid-cols-[minmax(0,1fr)_20rem]">
338
326
  <CommercePosCatalog :products="visibleProducts()" :categories="categories()" :search="search()" :category="categoryFilter()" :availability="availabilityFilter()" :currency="defaultCurrency()" :loading="loading()" @search="search.set($event)" @category="categoryFilter.set($event)" @availability="availabilityFilter.set($event)" @add="addProduct" />
@@ -268,29 +268,17 @@ onMount(() => {
268
268
  </script>
269
269
 
270
270
  <div class="space-y-6">
271
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
272
- <div>
273
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Products</h1>
274
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage native Product records, commerce availability, inventory, and relationships.</p>
275
- </div>
276
- <div class="flex flex-wrap gap-2">
277
- <Button variant="secondary" :loading="loading()" @click="loadProducts()"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
278
- <Button tag="a" variant="secondary" :href="exportHref()" :download="'products-' + new Date().toISOString().slice(0, 10) + '.csv'"><span class="h-4 w-4 i-hugeicons-download-04"></span>Export filtered</Button>
279
- <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add product</Button>
280
- </div>
281
- </header>
271
+ <PageHeader title="Products" description="Manage native Product records, commerce availability, inventory, and relationships.">
272
+ <div class="flex flex-wrap gap-2">
273
+ <Button variant="secondary" :loading="loading()" @click="loadProducts()"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
274
+ <Button tag="a" variant="secondary" :href="exportHref()" :download="'products-' + new Date().toISOString().slice(0, 10) + '.csv'"><span class="h-4 w-4 i-hugeicons-download-04"></span>Export filtered</Button>
275
+ <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add product</Button>
276
+ </div>
277
+ </PageHeader>
282
278
 
283
279
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
284
280
 
285
- <section aria-label="Product metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-5">
286
- <template :for="item in metricCards()" :key="item.label">
287
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
288
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
289
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
290
- <p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p>
291
- </article>
292
- </template>
293
- </section>
281
+ <MetricCards label="Product metrics" columns="grid-cols-2 lg:grid-cols-5" :items="metricCards()" />
294
282
 
295
283
  <section class="space-y-3">
296
284
  <div class="grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-5">
@@ -281,27 +281,16 @@ onMount(() => {
281
281
  </script>
282
282
 
283
283
  <div class="space-y-6">
284
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
285
- <div>
286
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">{{ title() }}</h1>
287
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ description() }}</p>
288
- </div>
289
- <Button variant="secondary" :loading="loading()" @click="loadReviews">
290
- <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
291
- Refresh
292
- </Button>
293
- </header>
284
+ <PageHeader :title="title()" :description="description()">
285
+ <Button variant="secondary" :loading="loading()" @click="loadReviews">
286
+ <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
287
+ Refresh
288
+ </Button>
289
+ </PageHeader>
294
290
 
295
291
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
296
292
 
297
- <section aria-label="Review metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-5">
298
- <template :for="item in metricCards()" :key="item.label">
299
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
300
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
301
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
302
- </article>
303
- </template>
304
- </section>
293
+ <MetricCards label="Review metrics" columns="grid-cols-2 lg:grid-cols-5" :items="metricCards()" />
305
294
 
306
295
  <section class="space-y-3">
307
296
  <div class="grid gap-3 grid-cols-1 md:grid-cols-2 xl:grid-cols-5">
@@ -203,22 +203,18 @@ onMount(() => {
203
203
  </script>
204
204
 
205
205
  <div class="space-y-6">
206
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
207
- <div>
208
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Tax rates</h1>
209
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage native TaxRate records used by commerce calculations.</p>
210
- </div>
211
- <div class="flex gap-2">
212
- <Button variant="secondary" :loading="loading()" @click="loadTaxes">
213
- <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
214
- Refresh
215
- </Button>
216
- <Button @click="openAdd()">
217
- <span class="h-4 w-4 i-hugeicons-add-01"></span>
218
- Add tax rate
219
- </Button>
220
- </div>
221
- </header>
206
+ <PageHeader title="Tax rates" description="Manage native TaxRate records used by commerce calculations.">
207
+ <div class="flex gap-2">
208
+ <Button variant="secondary" :loading="loading()" @click="loadTaxes">
209
+ <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
210
+ Refresh
211
+ </Button>
212
+ <Button @click="openAdd()">
213
+ <span class="h-4 w-4 i-hugeicons-add-01"></span>
214
+ Add tax rate
215
+ </Button>
216
+ </div>
217
+ </PageHeader>
222
218
 
223
219
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
224
220
  <div :if="summary().defaultConflicts > 0" class="flex gap-3 items-start p-4 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900">
@@ -226,14 +222,7 @@ onMount(() => {
226
222
  <p>{{ summary().defaultConflicts + 1 }} tax rates are marked as default. Save the intended default to clear the others.</p>
227
223
  </div>
228
224
 
229
- <section aria-label="Tax rate metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-4">
230
- <template :for="item in metricCards()" :key="item.label">
231
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
232
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
233
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
234
- </article>
235
- </template>
236
- </section>
225
+ <MetricCards label="Tax rate metrics" columns="grid-cols-2 lg:grid-cols-4" :items="metricCards()" />
237
226
 
238
227
  <section class="space-y-3">
239
228
  <div class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-5">
@@ -203,22 +203,18 @@ onMount(() => {
203
203
  </script>
204
204
 
205
205
  <div class="space-y-6">
206
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
207
- <div>
208
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Units</h1>
209
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage native ProductUnit records and their product relationships.</p>
210
- </div>
211
- <div class="flex gap-2">
212
- <Button variant="secondary" :loading="loading()" @click="loadUnits">
213
- <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
214
- Refresh
215
- </Button>
216
- <Button @click="openAdd()">
217
- <span class="h-4 w-4 i-hugeicons-add-01"></span>
218
- Add unit
219
- </Button>
220
- </div>
221
- </header>
206
+ <PageHeader title="Units" description="Manage native ProductUnit records and their product relationships.">
207
+ <div class="flex gap-2">
208
+ <Button variant="secondary" :loading="loading()" @click="loadUnits">
209
+ <span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>
210
+ Refresh
211
+ </Button>
212
+ <Button @click="openAdd()">
213
+ <span class="h-4 w-4 i-hugeicons-add-01"></span>
214
+ Add unit
215
+ </Button>
216
+ </div>
217
+ </PageHeader>
222
218
 
223
219
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
224
220
  <div :if="summary().defaultConflicts > 0" class="flex gap-3 items-start p-4 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900">
@@ -226,14 +222,7 @@ onMount(() => {
226
222
  <p>{{ summary().defaultConflicts }} unit {{ summary().defaultConflicts === 1 ? 'type has' : 'types have' }} competing defaults. Save the intended default to normalize that type.</p>
227
223
  </div>
228
224
 
229
- <section aria-label="Product unit metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-4">
230
- <template :for="item in metricCards()" :key="item.label">
231
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
232
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
233
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
234
- </article>
235
- </template>
236
- </section>
225
+ <MetricCards label="Product unit metrics" columns="grid-cols-2 lg:grid-cols-4" :items="metricCards()" />
237
226
 
238
227
  <section class="space-y-3">
239
228
  <div class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
@@ -198,27 +198,16 @@ onMount(() => {
198
198
  </script>
199
199
 
200
200
  <div class="space-y-6">
201
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
202
- <div>
203
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Product variants</h1>
204
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage native ProductVariant records, option sets, and product relationships.</p>
205
- </div>
206
- <div class="flex gap-2">
207
- <Button variant="secondary" :loading="loading()" @click="loadVariants"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
208
- <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add variant</Button>
209
- </div>
210
- </header>
201
+ <PageHeader title="Product variants" description="Manage native ProductVariant records, option sets, and product relationships.">
202
+ <div class="flex gap-2">
203
+ <Button variant="secondary" :loading="loading()" @click="loadVariants"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
204
+ <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add variant</Button>
205
+ </div>
206
+ </PageHeader>
211
207
 
212
208
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
213
209
 
214
- <section aria-label="Product variant metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-4">
215
- <template :for="item in metricCards()" :key="item.label">
216
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
217
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
218
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
219
- </article>
220
- </template>
221
- </section>
210
+ <MetricCards label="Product variant metrics" columns="grid-cols-2 lg:grid-cols-4" :items="metricCards()" />
222
211
 
223
212
  <section class="space-y-3">
224
213
  <div class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-5">
@@ -187,22 +187,16 @@ onMount(() => {
187
187
  <div class="space-y-6">
188
188
  <TabNavigation model-value="couriers" />
189
189
 
190
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
191
- <div>
192
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Delivery couriers</h1>
193
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage courier availability, vehicles, licenses, and optional user accounts.</p>
194
- </div>
195
- <div class="flex gap-2">
196
- <Button :loading="loading()" variant="secondary" @click="loadCouriers()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
197
- <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add courier</Button>
198
- </div>
199
- </header>
190
+ <PageHeader title="Delivery couriers" description="Manage courier availability, vehicles, licenses, and optional user accounts.">
191
+ <div class="flex gap-2">
192
+ <Button :loading="loading()" variant="secondary" @click="loadCouriers()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
193
+ <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add courier</Button>
194
+ </div>
195
+ </PageHeader>
200
196
 
201
197
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadCouriers()">Retry</Button></div>
202
198
 
203
- <section aria-label="Courier metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
204
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
205
- </section>
199
+ <MetricCards label="Courier metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
206
200
 
207
201
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
208
202
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -142,22 +142,18 @@ onMount(() => {
142
142
  <div class="space-y-6">
143
143
  <TabNavigation model-value="overview" />
144
144
 
145
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
146
- <div>
147
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Delivery</h1>
148
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Monitor active routes, courier availability, shipping methods, and coverage.</p>
149
- </div>
150
- <div class="flex gap-2">
151
- <Button :loading="loading()" variant="secondary" @click="loadOverview()">
152
- <span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>
153
- Refresh
154
- </Button>
155
- <Button href="/commerce/delivery/delivery-routes">
156
- <span aria-hidden="true" class="h-4 w-4 i-hugeicons-route-02"></span>
157
- Manage routes
158
- </Button>
159
- </div>
160
- </header>
145
+ <PageHeader title="Delivery" description="Monitor active routes, courier availability, shipping methods, and coverage.">
146
+ <div class="flex gap-2">
147
+ <Button :loading="loading()" variant="secondary" @click="loadOverview()">
148
+ <span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>
149
+ Refresh
150
+ </Button>
151
+ <Button href="/commerce/delivery/delivery-routes">
152
+ <span aria-hidden="true" class="h-4 w-4 i-hugeicons-route-02"></span>
153
+ Manage routes
154
+ </Button>
155
+ </div>
156
+ </PageHeader>
161
157
 
162
158
  <div :if="loadError()" role="alert" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">
163
159
  <span>{{ loadError() }}</span>
@@ -191,23 +191,17 @@ onMount(() => {
191
191
  <div class="space-y-6">
192
192
  <TabNavigation model-value="routes" />
193
193
 
194
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
195
- <div>
196
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Delivery routes</h1>
197
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Assign persisted couriers and track route workload, distance, and activity.</p>
198
- </div>
199
- <div class="flex gap-2">
200
- <Button :loading="loading()" variant="secondary" @click="loadRoutes()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
201
- <Button :disabled="loading() || couriers().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add route</Button>
202
- </div>
203
- </header>
194
+ <PageHeader title="Delivery routes" description="Assign persisted couriers and track route workload, distance, and activity.">
195
+ <div class="flex gap-2">
196
+ <Button :loading="loading()" variant="secondary" @click="loadRoutes()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
197
+ <Button :disabled="loading() || couriers().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add route</Button>
198
+ </div>
199
+ </PageHeader>
204
200
 
205
201
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadRoutes()">Retry</Button></div>
206
202
  <div :if="!loading() && couriers().length === 0" class="p-4 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900">Add a courier before creating a delivery route.</div>
207
203
 
208
- <section aria-label="Delivery route metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
209
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
210
- </section>
204
+ <MetricCards label="Delivery route metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
211
205
 
212
206
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
213
207
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -202,22 +202,16 @@ onMount(() => {
202
202
  <div class="space-y-6">
203
203
  <TabNavigation model-value="digital" />
204
204
 
205
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
206
- <div>
207
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Digital delivery</h1>
208
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Control file access, download limits, expiry, and automatic fulfillment.</p>
209
- </div>
210
- <div class="flex gap-2">
211
- <Button :loading="loading()" variant="secondary" @click="loadMethods()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
212
- <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add method</Button>
213
- </div>
214
- </header>
205
+ <PageHeader title="Digital delivery" description="Control file access, download limits, expiry, and automatic fulfillment.">
206
+ <div class="flex gap-2">
207
+ <Button :loading="loading()" variant="secondary" @click="loadMethods()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
208
+ <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add method</Button>
209
+ </div>
210
+ </PageHeader>
215
211
 
216
212
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadMethods()">Retry</Button></div>
217
213
 
218
- <section aria-label="Digital delivery metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
219
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
220
- </section>
214
+ <MetricCards label="Digital delivery metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
221
215
 
222
216
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
223
217
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -206,22 +206,16 @@ onMount(() => {
206
206
  <div class="space-y-6">
207
207
  <TabNavigation model-value="license" />
208
208
 
209
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
210
- <div>
211
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">License keys</h1>
212
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Generate unique software licenses and manage their commerce assignments.</p>
213
- </div>
214
- <div class="flex gap-2">
215
- <Button :loading="loading()" variant="secondary" @click="loadKeys()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
216
- <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add key</Button>
217
- </div>
218
- </header>
209
+ <PageHeader title="License keys" description="Generate unique software licenses and manage their commerce assignments.">
210
+ <div class="flex gap-2">
211
+ <Button :loading="loading()" variant="secondary" @click="loadKeys()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
212
+ <Button :disabled="loading()" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add key</Button>
213
+ </div>
214
+ </PageHeader>
219
215
 
220
216
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadKeys()">Retry</Button></div>
221
217
 
222
- <section aria-label="License key metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
223
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
224
- </section>
218
+ <MetricCards label="License key metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
225
219
 
226
220
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
227
221
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -209,22 +209,16 @@ onMount(() => {
209
209
  <div class="space-y-6">
210
210
  <TabNavigation model-value="methods" />
211
211
 
212
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
213
- <div>
214
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Shipping methods</h1>
215
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage persisted rates, activation state, and shipping-zone coverage.</p>
216
- </div>
217
- <div class="flex gap-2">
218
- <Button :loading="loading()" variant="secondary" @click="loadMethods()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
219
- <Button @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add method</Button>
220
- </div>
221
- </header>
212
+ <PageHeader title="Shipping methods" description="Manage persisted rates, activation state, and shipping-zone coverage.">
213
+ <div class="flex gap-2">
214
+ <Button :loading="loading()" variant="secondary" @click="loadMethods()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
215
+ <Button @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add method</Button>
216
+ </div>
217
+ </PageHeader>
222
218
 
223
219
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadMethods()">Retry</Button></div>
224
220
 
225
- <section aria-label="Shipping method metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-3">
226
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
227
- </section>
221
+ <MetricCards label="Shipping method metrics" columns="grid-cols-1 sm:grid-cols-3" :items="metricCards()" />
228
222
 
229
223
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
230
224
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -200,23 +200,17 @@ onMount(() => {
200
200
  <div class="space-y-6">
201
201
  <TabNavigation model-value="rates" />
202
202
 
203
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
204
- <div>
205
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Shipping rates</h1>
206
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Price method and zone combinations across exact weight bands.</p>
207
- </div>
208
- <div class="flex gap-2">
209
- <Button :loading="loading()" variant="secondary" @click="loadRates()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
210
- <Button :disabled="loading() || methods().length === 0 || zones().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add rate</Button>
211
- </div>
212
- </header>
203
+ <PageHeader title="Shipping rates" description="Price method and zone combinations across exact weight bands.">
204
+ <div class="flex gap-2">
205
+ <Button :loading="loading()" variant="secondary" @click="loadRates()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
206
+ <Button :disabled="loading() || methods().length === 0 || zones().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add rate</Button>
207
+ </div>
208
+ </PageHeader>
213
209
 
214
210
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadRates()">Retry</Button></div>
215
211
  <div :if="!loading() && (!methods().length || !zones().length)" class="p-4 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900">Add at least one shipping method and one shipping zone before creating a rate.</div>
216
212
 
217
- <section aria-label="Shipping rate metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
218
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
219
- </section>
213
+ <MetricCards label="Shipping rate metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
220
214
 
221
215
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
222
216
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -206,23 +206,17 @@ onMount(() => {
206
206
  <div class="space-y-6">
207
207
  <TabNavigation model-value="zones" />
208
208
 
209
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
210
- <div>
211
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Shipping zones</h1>
212
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Map methods to persisted country, region, and postal coverage.</p>
213
- </div>
214
- <div class="flex gap-2">
215
- <Button :loading="loading()" variant="secondary" @click="loadZones()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
216
- <Button :disabled="loading() || methods().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add zone</Button>
217
- </div>
218
- </header>
209
+ <PageHeader title="Shipping zones" description="Map methods to persisted country, region, and postal coverage.">
210
+ <div class="flex gap-2">
211
+ <Button :loading="loading()" variant="secondary" @click="loadZones()"><span :if="!loading()" aria-hidden="true" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
212
+ <Button :disabled="loading() || methods().length === 0" @click="openCreate()"><span aria-hidden="true" class="h-4 w-4 i-hugeicons-add-01"></span>Add zone</Button>
213
+ </div>
214
+ </PageHeader>
219
215
 
220
216
  <div :if="loadError()" class="flex gap-4 items-center justify-between p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"><span>{{ loadError() }}</span><Button variant="secondary" size="sm" @click="loadZones()">Retry</Button></div>
221
217
  <div :if="!loading() && methods().length === 0" class="p-4 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-lg dark:border-amber-900">Add a shipping method before creating a zone.</div>
222
218
 
223
- <section aria-label="Shipping zone metrics" class="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
224
- <template :for="item in metricCards()" :key="item.label"><article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p><p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p></article></template>
225
- </section>
219
+ <MetricCards label="Shipping zone metrics" columns="grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" :items="metricCards()" />
226
220
 
227
221
  <section class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
228
222
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-4 py-4 border-b border-gray-200 dark:border-neutral-700">
@@ -243,29 +243,17 @@ onMount(() => {
243
243
  </script>
244
244
 
245
245
  <div class="space-y-6">
246
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
247
- <div>
248
- <h1 class="font-bold text-2xl text-gray-900 dark:text-white">Receipt printers</h1>
249
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Manage native PrintDevice records and inspect their persisted operational state.</p>
250
- </div>
251
- <div class="flex flex-wrap gap-2">
252
- <Button variant="secondary" :loading="loading()" @click="loadPrintDevices"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
253
- <Button tag="a" variant="secondary" :href="exportHref()" :download="exportFilename()"><span class="h-4 w-4 i-hugeicons-download-04"></span>Export filtered</Button>
254
- <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add printer</Button>
255
- </div>
256
- </header>
246
+ <PageHeader title="Receipt printers" description="Manage native PrintDevice records and inspect their persisted operational state.">
247
+ <div class="flex flex-wrap gap-2">
248
+ <Button variant="secondary" :loading="loading()" @click="loadPrintDevices"><span :if="!loading()" class="h-4 w-4 i-hugeicons-refresh"></span>Refresh</Button>
249
+ <Button tag="a" variant="secondary" :href="exportHref()" :download="exportFilename()"><span class="h-4 w-4 i-hugeicons-download-04"></span>Export filtered</Button>
250
+ <Button @click="openAdd()"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add printer</Button>
251
+ </div>
252
+ </PageHeader>
257
253
 
258
254
  <div :if="loadError()" class="p-4 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">{{ loadError() }}</div>
259
255
 
260
- <section aria-label="Print device metrics" class="grid gap-3 grid-cols-2 lg:grid-cols-4">
261
- <template :for="item in metricCards()" :key="item.label">
262
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
263
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ item.label }}</p>
264
- <p class="mt-2 font-semibold text-2xl text-gray-900 dark:text-white">{{ item.value }}</p>
265
- <p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ item.detail }}</p>
266
- </article>
267
- </template>
268
- </section>
256
+ <MetricCards label="Print device metrics" columns="grid-cols-2 lg:grid-cols-4" :items="metricCards()" />
269
257
 
270
258
  <PrintDeviceBreakdown :records="records()" />
271
259