@stacksjs/defaults 0.70.362 → 0.70.365

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 (137) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +2 -1
  2. package/ai/skills/stacks-browse/scripts/browse.ts +20 -2
  3. package/ai/skills/stacks-dashboard/SKILL.md +4 -0
  4. package/app/Actions/Auth/LogoutAction.ts +11 -4
  5. package/app/Actions/Auth/LogoutAllAction.ts +8 -4
  6. package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
  7. package/app/Actions/Dashboard/Models/model-write.ts +5 -0
  8. package/ide/vscode/package.json +1 -1
  9. package/package.json +1 -1
  10. package/resources/components/Buttons/Counter.stx +1 -1
  11. package/resources/components/Buttons/ToggleDark.stx +1 -1
  12. package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
  13. package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
  14. package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
  15. package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
  16. package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
  17. package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
  18. package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
  19. package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
  20. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
  21. package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
  22. package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
  23. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
  24. package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
  25. package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
  26. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
  27. package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
  28. package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
  29. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
  30. package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
  31. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
  32. package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
  33. package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
  34. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
  35. package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
  36. package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
  37. package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
  38. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
  39. package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
  40. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
  41. package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
  42. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
  43. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
  44. package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
  45. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
  46. package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
  47. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
  48. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
  49. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
  50. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
  51. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
  52. package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
  53. package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
  54. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
  55. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
  56. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
  57. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
  58. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
  59. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
  60. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
  61. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
  62. package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
  63. package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
  64. package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
  65. package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
  66. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
  67. package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
  68. package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
  69. package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
  70. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
  71. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
  72. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
  73. package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
  74. package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
  75. package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
  76. package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
  77. package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
  78. package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
  79. package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
  80. package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
  81. package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
  82. package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
  83. package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
  84. package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
  85. package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
  86. package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
  87. package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
  88. package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
  89. package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
  90. package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
  91. package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
  92. package/resources/components/Dashboard/GlobalSearch.stx +3 -9
  93. package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
  94. package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
  95. package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
  96. package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
  97. package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
  98. package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
  99. package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
  100. package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
  101. package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
  102. package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
  103. package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
  104. package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
  105. package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
  106. package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
  107. package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
  108. package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
  109. package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
  110. package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
  111. package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
  112. package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
  113. package/resources/components/Dashboard/Navbar.stx +5 -5
  114. package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
  115. package/resources/components/Dashboard/UI/Button.stx +4 -0
  116. package/resources/components/Dashboard/UI/ConfirmDialog.stx +4 -0
  117. package/resources/components/Dashboard/UI/Drawer.stx +114 -0
  118. package/resources/components/Dashboard/UI/Modal.stx +19 -12
  119. package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
  120. package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
  121. package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
  122. package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
  123. package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
  124. package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
  125. package/resources/components/Docs/TeamMember.stx +16 -9
  126. package/resources/components/Forum/ForumReplyForm.stx +7 -7
  127. package/resources/components/Forum/ForumSidebar.stx +10 -6
  128. package/resources/components/Marketing/Faqs.stx +5 -5
  129. package/resources/components/Marketing/Feature.stx +11 -6
  130. package/resources/components/Marketing/Layout.stx +12 -8
  131. package/resources/components/Marketing/Pricing.stx +11 -5
  132. package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
  133. package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
  134. package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
  135. package/resources/components/Marketing/Testimonials.stx +8 -5
  136. package/resources/views/index.stx +4 -4
  137. package/views/system-tray/index.stx +1 -1
@@ -120,17 +120,18 @@ function errorMessages(): string[] {
120
120
  }
121
121
  </script>
122
122
 
123
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
124
- <button type="button" aria-label="Close category dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
125
- <section role="dialog" aria-modal="true" aria-labelledby="category-dialog-title" class="overflow-y-auto relative max-h-[90dvh] max-w-2xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
126
- <form @submit.prevent="submit">
127
- <div class="p-6">
128
- <h2 id="category-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit category' : 'Add category' }}</h2>
129
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store hierarchy fields declared by the Category model.</p>
130
- <ul :if="errorMessages().length > 0" class="mt-5 p-3 space-y-1 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">
123
+ <Modal
124
+ :isOpen="open()"
125
+ :title="mode() === 'edit' ? 'Edit category' : 'Add category'"
126
+ description="Store hierarchy fields declared by the Category model."
127
+ size="lg"
128
+ @close="emit('close')"
129
+ >
130
+ <form id="category-form" @submit.prevent="submit">
131
+ <ul :if="errorMessages().length > 0" class="mb-5 p-3 space-y-1 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">
131
132
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
132
133
  </ul>
133
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
134
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
134
135
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span><input x-model="name" type="text" maxlength="50" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
135
136
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Slug</span><input x-model="slug" type="text" required spellcheck="false" class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" @input="markSlugTouched" @blur="normalizeSlug" /></label>
136
137
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Description</span><textarea x-model="description" rows="3" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"></textarea></label>
@@ -139,8 +140,8 @@ function errorMessages(): string[] {
139
140
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Display order</span><input x-model="displayOrder" type="number" min="0" step="1" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
140
141
  <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Active in the storefront</span><span class="block mt-0.5 text-gray-400 text-xs dark:text-neutral-500">Availability is independent from the hierarchy.</span></span></label>
141
142
  </div>
142
- </div>
143
- <footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save category' : 'Add category' }}</Button></footer>
144
143
  </form>
145
- </section>
146
- </div>
144
+ <template #footer>
145
+ <div class="flex gap-2 justify-end w-full"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" form="category-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save category' : 'Add category' }}</Button></div>
146
+ </template>
147
+ </Modal>
@@ -5,12 +5,15 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as CouponRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${record()?.code || 'Unnamed coupon'}" permanently? Recorded order relationships may prevent deletion.`)
8
9
  </script>
9
10
 
10
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
11
- <button type="button" aria-label="Close delete coupon dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="coupon-delete-title" class="relative p-6 max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
13
- <h2 id="coupon-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete coupon</h2><p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Delete "{{ record()?.code || 'Unnamed coupon' }}" permanently? Recorded order relationships may prevent deletion.</p>
14
- <footer class="flex gap-2 justify-end mt-6"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button variant="danger" :loading="busy()" @click="emit('confirm')">Delete coupon</Button></footer>
15
- </section>
16
- </div>
11
+ <ConfirmDialog
12
+ :isOpen="open()"
13
+ title="Delete coupon"
14
+ :description="description()"
15
+ confirmLabel="Delete coupon"
16
+ :busy="busy()"
17
+ @close="emit('close')"
18
+ @confirm="emit('confirm')"
19
+ />
@@ -32,22 +32,28 @@ function deleteRecord(): void {
32
32
  }
33
33
  </script>
34
34
 
35
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
36
- <button type="button" aria-label="Close coupon details" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
37
- <section role="dialog" aria-modal="true" aria-labelledby="coupon-details-title" class="relative max-w-xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
38
- <div class="p-6"><div class="flex gap-4 items-start justify-between"><div><h2 id="coupon-details-title" class="font-mono font-semibold text-gray-900 text-lg dark:text-white">{{ record()?.code || 'Coupon' }}</h2><p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ record()?.description || 'No description recorded.' }}</p></div><span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span></div>
39
- <dl class="grid gap-4 grid-cols-2 mt-6">
40
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ discount(record()) }}</dd></div>
41
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
42
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Usage</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.usageCount || 0 }} / {{ record()?.usageLimit || 'No limit' }}</dd></div>
43
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Minimum order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.minOrderAmount ? money(record()?.minOrderAmount || 0) : 'No minimum' }}</dd></div>
44
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Maximum discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.maxDiscountAmount ? money(record()?.maxDiscountAmount || 0) : 'No maximum' }}</dd></div>
45
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Product relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.productId || 'Not linked' }}</dd></div>
46
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Free product reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.freeProductId || 'Not configured' }}</dd></div>
47
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Start date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.startDate || 'Not recorded' }}</dd></div>
48
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">End date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.endDate || 'Not recorded' }}</dd></div>
49
- </dl>
50
- </div>
51
- <footer class="flex gap-2 justify-between px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit coupon</Button></div></footer>
52
- </section>
53
- </div>
35
+ <Modal
36
+ :isOpen="open()"
37
+ :title="record()?.code || 'Coupon'"
38
+ :description="record()?.description || 'No description recorded.'"
39
+ size="lg"
40
+ @close="emit('close')"
41
+ >
42
+ <template #header-trailing>
43
+ <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span>
44
+ </template>
45
+ <dl class="grid gap-4 grid-cols-2">
46
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ discount(record()) }}</dd></div>
47
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
48
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Usage</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.usageCount || 0 }} / {{ record()?.usageLimit || 'No limit' }}</dd></div>
49
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Minimum order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.minOrderAmount ? money(record()?.minOrderAmount || 0) : 'No minimum' }}</dd></div>
50
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Maximum discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.maxDiscountAmount ? money(record()?.maxDiscountAmount || 0) : 'No maximum' }}</dd></div>
51
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Product relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.productId || 'Not linked' }}</dd></div>
52
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Free product reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.freeProductId || 'Not configured' }}</dd></div>
53
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Start date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.startDate || 'Not recorded' }}</dd></div>
54
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">End date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.endDate || 'Not recorded' }}</dd></div>
55
+ </dl>
56
+ <template #footer>
57
+ <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit coupon</Button></div></div>
58
+ </template>
59
+ </Modal>
@@ -87,15 +87,18 @@ function errorMessages(): string[] {
87
87
  }
88
88
  </script>
89
89
 
90
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
91
- <button type="button" aria-label="Close coupon dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
92
- <section role="dialog" aria-modal="true" aria-labelledby="coupon-dialog-title" class="overflow-y-auto relative max-h-[90dvh] max-w-2xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
93
- <form @submit.prevent="submit">
94
- <div class="p-6"><h2 id="coupon-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit coupon' : 'Add coupon' }}</h2><p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store fields declared by the Coupon model.</p>
95
- <ul :if="errorMessages().length > 0" class="mt-5 p-3 space-y-1 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">
90
+ <Modal
91
+ :isOpen="open()"
92
+ :title="mode() === 'edit' ? 'Edit coupon' : 'Add coupon'"
93
+ description="Store fields declared by the Coupon model."
94
+ size="lg"
95
+ @close="emit('close')"
96
+ >
97
+ <form id="coupon-form" @submit.prevent="submit">
98
+ <ul :if="errorMessages().length > 0" class="mb-5 p-3 space-y-1 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">
96
99
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
97
100
  </ul>
98
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
101
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
99
102
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Code</span><input x-model="code" type="text" maxlength="50" required spellcheck="false" class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm uppercase dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
100
103
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Status</span><select x-model="status" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"><option value="Active">Active</option><option value="Scheduled">Scheduled</option><option value="Expired">Expired</option></select></label>
101
104
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Description</span><textarea x-model="description" rows="3" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"></textarea></label>
@@ -109,8 +112,8 @@ function errorMessages(): string[] {
109
112
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">End date</span><input x-model="endDate" type="date" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
110
113
  <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Enabled for redemption</span><span class="block mt-0.5 text-gray-400 text-xs dark:text-neutral-500">Availability is stored separately from lifecycle status.</span></span></label>
111
114
  </div>
112
- </div>
113
- <footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save coupon' : 'Add coupon' }}</Button></footer>
114
115
  </form>
115
- </section>
116
- </div>
116
+ <template #footer>
117
+ <div class="flex gap-2 justify-end w-full"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" form="coupon-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save coupon' : 'Add coupon' }}</Button></div>
118
+ </template>
119
+ </Modal>
@@ -5,13 +5,15 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as CommerceCustomerRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${record()?.name || 'Unnamed customer'}" permanently? Linked orders, reviews, payments, and gift cards may prevent deletion.`)
8
9
  </script>
9
10
 
10
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
11
- <button type="button" aria-label="Close delete customer dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="customer-delete-title" class="relative p-6 max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
13
- <h2 id="customer-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete customer</h2>
14
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Delete "{{ record()?.name || 'Unnamed customer' }}" permanently? Linked orders, reviews, payments, and gift cards may prevent deletion.</p>
15
- <footer class="flex gap-2 justify-end mt-6"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button variant="danger" :loading="busy()" @click="emit('confirm')">Delete customer</Button></footer>
16
- </section>
17
- </div>
11
+ <ConfirmDialog
12
+ :isOpen="open()"
13
+ title="Delete customer"
14
+ :description="description()"
15
+ confirmLabel="Delete customer"
16
+ :busy="busy()"
17
+ @close="emit('close')"
18
+ @confirm="emit('confirm')"
19
+ />
@@ -46,19 +46,27 @@ function deleteRecord(): void {
46
46
  }
47
47
  </script>
48
48
 
49
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
50
- <button type="button" aria-label="Close customer details" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
51
- <section role="dialog" aria-modal="true" aria-labelledby="customer-details-title" class="relative max-w-xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
52
- <div class="p-6">
53
- <div class="flex gap-4 items-start justify-between"><div class="flex gap-3 items-center"><span class="flex items-center justify-center h-11 w-11 font-semibold text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-lg">{{ (record()?.name || '?').slice(0, 1).toUpperCase() }}</span><div><h2 id="customer-details-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ record()?.name || 'Customer' }}</h2><p class="mt-1 text-gray-400 text-sm dark:text-neutral-500">{{ record()?.email || 'No email recorded' }}</p></div></div><span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Unknown' }}</span></div>
54
- <dl class="grid gap-4 grid-cols-2 mt-6">
55
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Phone</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.phone || 'Not recorded' }}</dd></div>
56
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Recorded spend</dt><dd class="mt-1 font-medium text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalSpent || 0) }}</dd></div>
57
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Last order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.lastOrder || '') }}</dd></div>
58
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
59
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Avatar URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.avatar || 'Not configured' }}</dd></div>
60
- </dl>
61
- </div>
62
- <footer class="flex gap-2 justify-between px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit customer</Button></div></footer>
63
- </section>
64
- </div>
49
+ <Modal
50
+ :isOpen="open()"
51
+ :title="record()?.name || 'Customer'"
52
+ :description="record()?.email || 'No email recorded'"
53
+ size="lg"
54
+ @close="emit('close')"
55
+ >
56
+ <template #header-leading>
57
+ <span class="flex items-center justify-center h-11 w-11 font-semibold text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-lg">{{ (record()?.name || '?').slice(0, 1).toUpperCase() }}</span>
58
+ </template>
59
+ <template #header-trailing>
60
+ <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Unknown' }}</span>
61
+ </template>
62
+ <dl class="grid gap-4 grid-cols-2">
63
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Phone</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.phone || 'Not recorded' }}</dd></div>
64
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Recorded spend</dt><dd class="mt-1 font-medium text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalSpent || 0) }}</dd></div>
65
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Last order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.lastOrder || '') }}</dd></div>
66
+ <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
67
+ <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Avatar URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.avatar || 'Not configured' }}</dd></div>
68
+ </dl>
69
+ <template #footer>
70
+ <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit customer</Button></div></div>
71
+ </template>
72
+ </Modal>
@@ -79,17 +79,18 @@ function errorMessages(): string[] {
79
79
  }
80
80
  </script>
81
81
 
82
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
83
- <button type="button" aria-label="Close customer dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
84
- <section role="dialog" aria-modal="true" aria-labelledby="customer-dialog-title" class="overflow-y-auto relative max-h-[90dvh] max-w-2xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
85
- <form @submit.prevent="submit">
86
- <div class="p-6">
87
- <h2 id="customer-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit customer' : 'Add customer' }}</h2>
88
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store fields declared by the native Customer model.</p>
89
- <ul :if="errorMessages().length > 0" class="mt-5 p-3 space-y-1 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">
82
+ <Modal
83
+ :isOpen="open()"
84
+ :title="mode() === 'edit' ? 'Edit customer' : 'Add customer'"
85
+ description="Store fields declared by the native Customer model."
86
+ size="lg"
87
+ @close="emit('close')"
88
+ >
89
+ <form id="customer-form" @submit.prevent="submit">
90
+ <ul :if="errorMessages().length > 0" class="mb-5 p-3 space-y-1 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">
90
91
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
91
92
  </ul>
92
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
93
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
93
94
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span><input x-model="name" type="text" minlength="2" maxlength="255" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
94
95
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Email</span><input x-model="email" type="email" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
95
96
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Phone <span class="font-normal text-gray-400 dark:text-neutral-500">(optional)</span></span><input x-model="phone" type="tel" maxlength="50" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
@@ -98,8 +99,8 @@ function errorMessages(): string[] {
98
99
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Last order</span><input x-model="lastOrder" type="date" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
99
100
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Avatar URL</span><input x-model="avatar" type="url" placeholder="https://cdn.example.com/customers/avatar.jpg" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /><span class="block mt-1 text-gray-400 text-xs dark:text-neutral-500">Use a persisted HTTP asset URL. The dashboard uses initials when rendering the table.</span></label>
100
101
  </div>
101
- </div>
102
- <footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save customer' : 'Add customer' }}</Button></footer>
103
102
  </form>
104
- </section>
105
- </div>
103
+ <template #footer>
104
+ <div class="flex gap-2 justify-end w-full"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" form="customer-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save customer' : 'Add customer' }}</Button></div>
105
+ </template>
106
+ </Modal>
@@ -5,13 +5,15 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as GiftCardRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${record()?.code || 'Unnamed gift card'}" permanently? Recorded customer or order relationships may prevent deletion.`)
8
9
  </script>
9
10
 
10
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
11
- <button type="button" aria-label="Close delete gift card dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="gift-card-delete-title" class="relative p-6 max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
13
- <h2 id="gift-card-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete gift card</h2>
14
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Delete "{{ record()?.code || 'Unnamed gift card' }}" permanently? Recorded customer or order relationships may prevent deletion.</p>
15
- <footer class="flex gap-2 justify-end mt-6"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button variant="danger" :loading="busy()" @click="emit('confirm')">Delete gift card</Button></footer>
16
- </section>
17
- </div>
11
+ <ConfirmDialog
12
+ :isOpen="open()"
13
+ title="Delete gift card"
14
+ :description="description()"
15
+ confirmLabel="Delete gift card"
16
+ :busy="busy()"
17
+ @close="emit('close')"
18
+ @confirm="emit('confirm')"
19
+ />
@@ -38,15 +38,17 @@ function deleteRecord(): void {
38
38
  }
39
39
  </script>
40
40
 
41
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
42
- <button type="button" aria-label="Close gift card details" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
43
- <section role="dialog" aria-modal="true" aria-labelledby="gift-card-details-title" class="overflow-y-auto relative max-h-[90dvh] max-w-2xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
44
- <div class="p-6">
45
- <div class="flex gap-4 items-start justify-between">
46
- <div><h2 id="gift-card-details-title" class="font-mono font-semibold text-gray-900 text-lg dark:text-white">{{ record()?.code || 'Gift card' }}</h2><p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ record()?.recipientName || 'No recipient assigned' }}</p></div>
47
- <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span>
48
- </div>
49
- <dl class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-6">
41
+ <Modal
42
+ :isOpen="open()"
43
+ :title="record()?.code || 'Gift card'"
44
+ :description="record()?.recipientName || 'No recipient assigned'"
45
+ size="lg"
46
+ @close="emit('close')"
47
+ >
48
+ <template #header-trailing>
49
+ <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span>
50
+ </template>
51
+ <dl class="grid gap-4 grid-cols-1 sm:grid-cols-2">
50
52
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Current balance</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ money(record()?.currentBalance || 0, record()?.currency || 'USD') }}</dd></div>
51
53
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Initial balance</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ money(record()?.initialBalance || 0, record()?.currency || 'USD') }}</dd></div>
52
54
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
@@ -59,10 +61,10 @@ function deleteRecord(): void {
59
61
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Last used date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ dateTime(record()?.lastUsedDate || '') }}</dd></div>
60
62
  <div class="sm:col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Personal message</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.personalMessage || 'No message recorded.' }}</dd></div>
61
63
  </dl>
62
- </div>
63
- <footer class="flex gap-2 justify-between px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700">
64
+ <template #footer>
65
+ <div class="flex gap-2 justify-between w-full">
64
66
  <Button variant="danger" @click="deleteRecord()">Delete</Button>
65
67
  <div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit gift card</Button></div>
66
- </footer>
67
- </section>
68
- </div>
68
+ </div>
69
+ </template>
70
+ </Modal>
@@ -152,17 +152,18 @@ function errorMessages(): string[] {
152
152
  }
153
153
  </script>
154
154
 
155
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
156
- <button type="button" aria-label="Close gift card dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
157
- <section role="dialog" aria-modal="true" aria-labelledby="gift-card-dialog-title" class="overflow-y-auto relative max-h-[92dvh] max-w-3xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
158
- <form @submit.prevent="submit">
159
- <div class="p-6">
160
- <h2 id="gift-card-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit gift card' : 'Add gift card' }}</h2>
161
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store fields declared by the GiftCard model.</p>
162
- <ul :if="errorMessages().length > 0" class="mt-5 p-3 space-y-1 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">
155
+ <Modal
156
+ :isOpen="open()"
157
+ :title="mode() === 'edit' ? 'Edit gift card' : 'Add gift card'"
158
+ description="Store fields declared by the GiftCard model."
159
+ size="xl"
160
+ @close="emit('close')"
161
+ >
162
+ <form id="gift-card-form" @submit.prevent="submit">
163
+ <ul :if="errorMessages().length > 0" class="mb-5 p-3 space-y-1 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
164
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
164
165
  </ul>
165
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
166
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
166
167
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Code</span><input x-model="code" type="text" maxlength="50" required spellcheck="false" class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm uppercase dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
167
168
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Initial balance</span><input x-model="initialBalance" type="number" min="1" step="0.01" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" @input="syncCurrentBalance($event)" /></label>
168
169
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Current balance</span><input x-model="currentBalance" :max="initialBalance()" type="number" min="0" step="0.01" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
@@ -183,11 +184,11 @@ function errorMessages(): string[] {
183
184
  <label class="flex gap-3 items-center p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isReloadable" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Reloadable</span></label>
184
185
  </div>
185
186
  </div>
186
- </div>
187
- <footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700">
188
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
189
- <Button type="submit" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save gift card' : 'Add gift card' }}</Button>
190
- </footer>
191
187
  </form>
192
- </section>
193
- </div>
188
+ <template #footer>
189
+ <div class="flex gap-2 justify-end w-full">
190
+ <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
191
+ <Button type="submit" form="gift-card-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save gift card' : 'Add gift card' }}</Button>
192
+ </div>
193
+ </template>
194
+ </Modal>
@@ -5,21 +5,19 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as ManufacturerRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${record()?.name || 'Unnamed manufacturer'}" permanently?`)
9
+ const warning = derived(() => record()?.productCount
10
+ ? `It is linked to ${record()?.productCount} products and deletion may be rejected.`
11
+ : '')
8
12
  </script>
9
13
 
10
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
11
- <button type="button" aria-label="Close delete manufacturer dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="manufacturer-delete-title" class="relative p-6 max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
13
- <h2 id="manufacturer-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete manufacturer</h2>
14
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">
15
- Delete "{{ record()?.name || 'Unnamed manufacturer' }}" permanently?
16
- <span :if="record()?.productCount">It is linked to {{ record()?.productCount }} products and deletion may be rejected.</span>
17
- </p>
18
- <footer class="flex gap-2 justify-end mt-6">
19
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
20
- <Button variant="danger" :loading="busy()" @click="emit('confirm')">
21
- Delete manufacturer
22
- </Button>
23
- </footer>
24
- </section>
25
- </div>
14
+ <ConfirmDialog
15
+ :isOpen="open()"
16
+ title="Delete manufacturer"
17
+ :description="description()"
18
+ :warning="warning()"
19
+ confirmLabel="Delete manufacturer"
20
+ :busy="busy()"
21
+ @close="emit('close')"
22
+ @confirm="emit('confirm')"
23
+ />
@@ -48,17 +48,18 @@ function errorMessages(): string[] {
48
48
  }
49
49
  </script>
50
50
 
51
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
52
- <button type="button" aria-label="Close manufacturer dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
53
- <section role="dialog" aria-modal="true" aria-labelledby="manufacturer-dialog-title" class="relative max-w-lg w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
54
- <form @submit.prevent="submit">
55
- <div class="p-6">
56
- <h2 id="manufacturer-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit manufacturer' : 'Add manufacturer' }}</h2>
57
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store only fields declared by the Manufacturer model.</p>
58
- <ul :if="errorMessages().length > 0" class="mt-5 p-3 space-y-1 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">
51
+ <Modal
52
+ :isOpen="open()"
53
+ :title="mode() === 'edit' ? 'Edit manufacturer' : 'Add manufacturer'"
54
+ description="Store only fields declared by the Manufacturer model."
55
+ size="md"
56
+ @close="emit('close')"
57
+ >
58
+ <form id="manufacturer-form" @submit.prevent="submit">
59
+ <ul :if="errorMessages().length > 0" class="mb-5 p-3 space-y-1 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">
59
60
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
60
61
  </ul>
61
- <div class="mt-5 space-y-4">
62
+ <div class="space-y-4">
62
63
  <label class="block">
63
64
  <span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
64
65
  <input x-model="name" type="text" maxlength="100" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" />
@@ -76,13 +77,13 @@ function errorMessages(): string[] {
76
77
  <span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Featured manufacturer</span>
77
78
  </label>
78
79
  </div>
79
- </div>
80
- <footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700">
80
+ </form>
81
+ <template #footer>
82
+ <div class="flex gap-2 justify-end w-full">
81
83
  <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
82
- <Button type="submit" :loading="busy()" :disabled="!name().trim() || !country().trim()">
84
+ <Button type="submit" form="manufacturer-form" :loading="busy()" :disabled="!name().trim() || !country().trim()">
83
85
  {{ mode() === 'edit' ? 'Save manufacturer' : 'Add manufacturer' }}
84
86
  </Button>
85
- </footer>
86
- </form>
87
- </section>
88
- </div>
87
+ </div>
88
+ </template>
89
+ </Modal>
@@ -5,13 +5,15 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as CommerceOrderRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete order ${record()?.id || 'not recorded'} permanently? Linked items, payments, and license keys may prevent deletion.`)
8
9
  </script>
9
10
 
10
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
11
- <button type="button" aria-label="Close delete order dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="order-delete-title" class="relative p-6 max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
13
- <h2 id="order-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete order</h2>
14
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Delete order {{ record()?.id || 'not recorded' }} permanently? Linked items, payments, and license keys may prevent deletion.</p>
15
- <footer class="flex gap-2 justify-end mt-6"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button variant="danger" :loading="busy()" @click="emit('confirm')">Delete order</Button></footer>
16
- </section>
17
- </div>
11
+ <ConfirmDialog
12
+ :isOpen="open()"
13
+ title="Delete order"
14
+ :description="description()"
15
+ confirmLabel="Delete order"
16
+ :busy="busy()"
17
+ @close="emit('close')"
18
+ @confirm="emit('confirm')"
19
+ />