@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
@@ -47,12 +47,17 @@ function deleteRecord(): void {
47
47
  }
48
48
  </script>
49
49
 
50
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
51
- <button type="button" aria-label="Close order details" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
52
- <section role="dialog" aria-modal="true" aria-labelledby="order-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">
53
- <div class="p-6">
54
- <div class="flex gap-4 items-start justify-between"><div><h2 id="order-details-title" class="font-semibold text-gray-900 text-lg dark:text-white">Order {{ record()?.id || 'not recorded' }}</h2><p class="mt-1 text-gray-400 text-sm dark:text-neutral-500">{{ record()?.customerName || 'Unassigned customer' }}</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">{{ label(record()?.status || 'UNKNOWN') }}</span></div>
55
- <dl class="grid gap-4 grid-cols-2 mt-6">
50
+ <Modal
51
+ :isOpen="open()"
52
+ :title="`Order ${record()?.id || 'not recorded'}`"
53
+ :description="record()?.customerName || 'Unassigned customer'"
54
+ size="lg"
55
+ @close="emit('close')"
56
+ >
57
+ <template #header-trailing>
58
+ <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">{{ label(record()?.status || 'UNKNOWN') }}</span>
59
+ </template>
60
+ <dl class="grid gap-4 grid-cols-2">
56
61
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Total</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalAmount || 0, record()?.currency || 'USD') }}</dd></div>
57
62
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Order type</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ label(record()?.orderType || 'UNKNOWN') }}</dd></div>
58
63
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Items</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.itemCount || 0 }}</dd></div>
@@ -67,7 +72,7 @@ function deleteRecord(): void {
67
72
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Coupon relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.couponId || 'Not linked' }}</dd></div>
68
73
  <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Applied coupon reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.appliedCouponId || 'Not linked' }}</dd></div>
69
74
  </dl>
70
- </div>
71
- <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 order</Button></div></footer>
72
- </section>
73
- </div>
75
+ <template #footer>
76
+ <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 order</Button></div></div>
77
+ </template>
78
+ </Modal>
@@ -107,17 +107,18 @@ function errorMessages(): string[] {
107
107
  }
108
108
  </script>
109
109
 
110
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
111
- <button type="button" aria-label="Close order dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
112
- <section role="dialog" aria-modal="true" aria-labelledby="order-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">
113
- <form @submit.prevent="submit">
114
- <div class="p-6">
115
- <h2 id="order-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit order' : 'Add order' }}</h2>
116
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store fields and relationships declared by the native Order model.</p>
117
- <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">
110
+ <Modal
111
+ :isOpen="open()"
112
+ :title="mode() === 'edit' ? 'Edit order' : 'Add order'"
113
+ description="Store fields and relationships declared by the native Order model."
114
+ size="xl"
115
+ @close="emit('close')"
116
+ >
117
+ <form id="order-form" @submit.prevent="submit">
118
+ <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">
118
119
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
119
120
  </ul>
120
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
121
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
121
122
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Customer</span><select x-model="customerId" 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="">Unassigned customer</option><template :for="option in customers()" :key="option.id"><option :value="option.id">{{ option.label }}</option></template></select></label>
122
123
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Status</span><select x-model="status" 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"><template :for="option in statuses()" :key="option"><option :value="option">{{ label(option) }}</option></template></select></label>
123
124
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Order type</span><select x-model="orderType" 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"><template :for="option in orderTypes()" :key="option"><option :value="option">{{ label(option) }}</option></template></select></label>
@@ -133,8 +134,8 @@ function errorMessages(): string[] {
133
134
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Delivery address</span><textarea x-model="deliveryAddress" 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>
134
135
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Special instructions</span><textarea x-model="specialInstructions" 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>
135
136
  </div>
136
- </div>
137
- <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 order' : 'Add order' }}</Button></footer>
138
137
  </form>
139
- </section>
140
- </div>
138
+ <template #footer>
139
+ <div class="flex gap-2 justify-end w-full"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" form="order-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save order' : 'Add order' }}</Button></div>
140
+ </template>
141
+ </Modal>
@@ -56,30 +56,35 @@ function submit(): void {
56
56
  }
57
57
  </script>
58
58
 
59
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
60
- <button type="button" aria-label="Close checkout dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
61
- <section role="dialog" aria-modal="true" aria-labelledby="pos-checkout-title" class="overflow-y-auto relative max-h-[92dvh] max-w-lg w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
62
- <form @submit.prevent="submit">
63
- <div class="p-6">
64
- <h2 id="pos-checkout-title" class="font-semibold text-gray-900 text-lg dark:text-white">Review cash sale</h2>
65
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">The server will recompute prices and tax before the atomic write.</p>
66
- <div :if="error()" class="mt-4 p-3 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">{{ error() }}</div>
59
+ <Modal
60
+ :isOpen="open()"
61
+ title="Review cash sale"
62
+ description="The server will recompute prices and tax before the atomic write."
63
+ :closable="!busy()"
64
+ @close="emit('close')"
65
+ >
66
+ <form id="pos-checkout-form" @submit.prevent="submit">
67
+ <div :if="error()" class="mb-4 p-3 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">{{ error() }}</div>
67
68
 
68
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
69
- <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Customer</span><select x-model="customerId" 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="">Guest sale</option><template :for="customer in customers()" :key="customer.id"><option :value="customer.id">{{ customer.label }}{{ customer.email ? ' · ' + customer.email : '' }}</option></template></select></label>
70
- <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Order type</span><select x-model="orderType" 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="TAKEOUT">Takeout</option><option value="DINE_IN">Dine in</option></select></label>
71
- <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Payment</span><select x-model="paymentMethod" 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"><template :for="method in paymentMethods()" :key="method.id"><option :value="method.id">{{ method.label }}</option></template></select></label>
72
- <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Order instructions</span><textarea x-model="specialInstructions" rows="3" maxlength="1000" placeholder="Optional instructions for the whole sale" 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>
73
- </div>
69
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
70
+ <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Customer</span><select x-model="customerId" 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="">Guest sale</option><template :for="customer in customers()" :key="customer.id"><option :value="customer.id">{{ customer.label }}{{ customer.email ? ' · ' + customer.email : '' }}</option></template></select></label>
71
+ <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Order type</span><select x-model="orderType" 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="TAKEOUT">Takeout</option><option value="DINE_IN">Dine in</option></select></label>
72
+ <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Payment</span><select x-model="paymentMethod" 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"><template :for="method in paymentMethods()" :key="method.id"><option :value="method.id">{{ method.label }}</option></template></select></label>
73
+ <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Order instructions</span><textarea x-model="specialInstructions" rows="3" maxlength="1000" placeholder="Optional instructions for the whole sale" 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>
74
+ </div>
74
75
 
75
- <dl class="mt-5 p-4 space-y-2 bg-gray-50 dark:bg-neutral-900/60 rounded-lg">
76
- <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Units</dt><dd>{{ unitCount() }}</dd></div>
77
- <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Subtotal</dt><dd>{{ formatMoney(subtotal()) }}</dd></div>
78
- <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Tax</dt><dd>{{ formatMoney(taxAmount()) }}</dd></div>
79
- <div class="flex items-center justify-between pt-2 font-semibold text-gray-900 dark:text-white border-gray-200 border-t dark:border-neutral-700"><dt>Cash due</dt><dd>{{ formatMoney(totalAmount()) }}</dd></div>
80
- </dl>
81
- </div>
82
- <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()">Complete sale</Button></footer>
83
- </form>
84
- </section>
85
- </div>
76
+ <dl class="mt-5 p-4 space-y-2 bg-gray-50 dark:bg-neutral-900/60 rounded-lg">
77
+ <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Units</dt><dd>{{ unitCount() }}</dd></div>
78
+ <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Subtotal</dt><dd>{{ formatMoney(subtotal()) }}</dd></div>
79
+ <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Tax</dt><dd>{{ formatMoney(taxAmount()) }}</dd></div>
80
+ <div class="flex items-center justify-between pt-2 font-semibold text-gray-900 dark:text-white border-gray-200 border-t dark:border-neutral-700"><dt>Cash due</dt><dd>{{ formatMoney(totalAmount()) }}</dd></div>
81
+ </dl>
82
+ </form>
83
+
84
+ <template #footer>
85
+ <div class="flex gap-2 justify-end w-full">
86
+ <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
87
+ <Button type="submit" form="pos-checkout-form" :loading="busy()">Complete sale</Button>
88
+ </div>
89
+ </template>
90
+ </Modal>
@@ -44,25 +44,32 @@ function formatDate(value: string): string {
44
44
  }
45
45
  </script>
46
46
 
47
- <div :if="open() && receipt()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
48
- <button type="button" aria-label="Close receipt dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
49
- <section role="dialog" aria-modal="true" aria-labelledby="pos-receipt-title" class="overflow-y-auto relative max-h-[92dvh] max-w-md w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
50
- <div class="p-6">
51
- <div class="flex items-center justify-center h-11 w-11 text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/40 rounded-full"><span class="h-6 w-6 i-hugeicons-checkmark-circle-02"></span></div>
52
- <h2 id="pos-receipt-title" class="mt-4 font-semibold text-gray-900 text-lg dark:text-white">Sale recorded</h2>
53
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Order {{ receipt()?.orderId }} · {{ formatDate(receipt()?.createdAt || '') }}</p>
54
- <p :if="receipt()?.referenceNumber" class="mt-1 font-mono text-gray-400 text-xs dark:text-neutral-500">{{ receipt()?.referenceNumber }}</p>
47
+ <Modal
48
+ :isOpen="open() && Boolean(receipt())"
49
+ title="Sale recorded"
50
+ :description="`Order ${receipt()?.orderId || ''} · ${formatDate(receipt()?.createdAt || '')}`"
51
+ size="sm"
52
+ @close="emit('close')"
53
+ >
54
+ <template #header-leading>
55
+ <div class="flex flex-shrink-0 items-center justify-center h-11 w-11 text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/40 rounded-full">
56
+ <span class="h-6 w-6 i-hugeicons-checkmark-circle-02"></span>
57
+ </div>
58
+ </template>
55
59
 
56
- <ul class="mt-5 divide-gray-100 divide-y dark:divide-neutral-700">
57
- <template :for="line in (receipt()?.lines || [])" :key="line.productId"><li class="py-3"><div class="flex gap-3 items-start justify-between"><div><p class="font-medium text-gray-900 text-sm dark:text-white">{{ line.quantity }} × {{ line.name }}</p><p :if="line.specialInstructions" class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ line.specialInstructions }}</p></div><p class="font-medium text-gray-700 text-sm whitespace-nowrap dark:text-neutral-200">{{ formatMoney(line.lineTotal) }}</p></div></li></template>
58
- </ul>
60
+ <p :if="receipt()?.referenceNumber" class="font-mono text-gray-400 text-xs dark:text-neutral-500">{{ receipt()?.referenceNumber }}</p>
59
61
 
60
- <dl class="mt-4 p-4 space-y-2 bg-gray-50 dark:bg-neutral-900/60 rounded-lg">
61
- <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Subtotal</dt><dd>{{ formatMoney(receipt()?.subtotal || 0) }}</dd></div>
62
- <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Tax<span :if="receipt()?.taxRate"> ({{ receipt()?.taxRate }}%)</span></dt><dd>{{ formatMoney(receipt()?.taxAmount || 0) }}</dd></div>
63
- <div class="flex items-center justify-between pt-2 font-semibold text-gray-900 dark:text-white border-gray-200 border-t dark:border-neutral-700"><dt>Cash paid</dt><dd>{{ formatMoney(receipt()?.totalAmount || 0) }}</dd></div>
64
- </dl>
65
- </div>
66
- <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 @click="emit('close')">Start next sale</Button></footer>
67
- </section>
68
- </div>
62
+ <ul class="mt-5 divide-gray-100 divide-y dark:divide-neutral-700">
63
+ <template :for="line in (receipt()?.lines || [])" :key="line.productId"><li class="py-3"><div class="flex gap-3 items-start justify-between"><div><p class="font-medium text-gray-900 text-sm dark:text-white">{{ line.quantity }} × {{ line.name }}</p><p :if="line.specialInstructions" class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ line.specialInstructions }}</p></div><p class="font-medium text-gray-700 text-sm whitespace-nowrap dark:text-neutral-200">{{ formatMoney(line.lineTotal) }}</p></div></li></template>
64
+ </ul>
65
+
66
+ <dl class="mt-4 p-4 space-y-2 bg-gray-50 dark:bg-neutral-900/60 rounded-lg">
67
+ <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Subtotal</dt><dd>{{ formatMoney(receipt()?.subtotal || 0) }}</dd></div>
68
+ <div class="flex items-center justify-between text-gray-500 text-sm dark:text-neutral-400"><dt>Tax<span :if="receipt()?.taxRate"> ({{ receipt()?.taxRate }}%)</span></dt><dd>{{ formatMoney(receipt()?.taxAmount || 0) }}</dd></div>
69
+ <div class="flex items-center justify-between pt-2 font-semibold text-gray-900 dark:text-white border-gray-200 border-t dark:border-neutral-700"><dt>Cash paid</dt><dd>{{ formatMoney(receipt()?.totalAmount || 0) }}</dd></div>
70
+ </dl>
71
+
72
+ <template #footer>
73
+ <Button @click="emit('close')">Start next sale</Button>
74
+ </template>
75
+ </Modal>
@@ -5,19 +5,19 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as CommerceProductRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${record()?.name || 'Unnamed product'}" permanently?`)
9
+ const warning = derived(() => record()?.variantCount || record()?.unitCount || record()?.reviewCount
10
+ ? 'Related variants, units, or reviews may cause deletion to 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 product dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="product-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="product-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete product</h2>
14
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">
15
- Delete "{{ record()?.name || 'Unnamed product' }}" permanently?
16
- <span :if="record()?.variantCount || record()?.unitCount || record()?.reviewCount">Related variants, units, or reviews may cause deletion to 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')">Delete product</Button>
21
- </footer>
22
- </section>
23
- </div>
14
+ <ConfirmDialog
15
+ :isOpen="open()"
16
+ title="Delete product"
17
+ :description="description()"
18
+ :warning="warning()"
19
+ confirmLabel="Delete product"
20
+ :busy="busy()"
21
+ @close="emit('close')"
22
+ @confirm="emit('confirm')"
23
+ />
@@ -107,17 +107,18 @@ function errorMessages(): string[] {
107
107
  }
108
108
  </script>
109
109
 
110
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
111
- <button type="button" aria-label="Close product dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
112
- <section role="dialog" aria-modal="true" aria-labelledby="product-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">
113
- <form @submit.prevent="submit">
114
- <div class="p-6">
115
- <h2 id="product-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit product' : 'Add product' }}</h2>
116
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Store fields and belongs-to relationships declared by the native Product model.</p>
117
- <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">
110
+ <Modal
111
+ :isOpen="open()"
112
+ :title="mode() === 'edit' ? 'Edit product' : 'Add product'"
113
+ description="Store fields and belongs-to relationships declared by the native Product model."
114
+ size="xl"
115
+ @close="emit('close')"
116
+ >
117
+ <form id="product-form" @submit.prevent="submit">
118
+ <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">
118
119
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
119
120
  </ul>
120
- <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-5">
121
+ <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
121
122
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span><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" /></label>
122
123
  <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>
123
124
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Price ({{ currency() }})</span><input x-model="price" 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" /></label>
@@ -130,8 +131,8 @@ function errorMessages(): string[] {
130
131
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Allergens</span><input x-model="allergens" type="text" placeholder="Comma-separated, for example: Gluten, Soy" 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>
131
132
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Nutritional information (JSON object)</span><textarea x-model="nutritionalInfo" rows="4" spellcheck="false" :aria-invalid="!nutritionValid()" 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"></textarea><span :if="!nutritionValid()" class="block mt-1 text-red-600 text-xs dark:text-red-400">Enter a valid JSON object.</span></label>
132
133
  </div>
133
- </div>
134
- <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 product' : 'Add product' }}</Button></footer>
135
134
  </form>
136
- </section>
137
- </div>
135
+ <template #footer>
136
+ <div class="flex gap-2 justify-end w-full"><Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button><Button type="submit" form="product-form" :loading="busy()" :disabled="!valid()">{{ mode() === 'edit' ? 'Save product' : 'Add product' }}</Button></div>
137
+ </template>
138
+ </Modal>
@@ -5,23 +5,15 @@ const open = useReactiveProp('open', false)
5
5
  const review = useReactiveProp('review', null as ReviewRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `Delete "${review()?.title || 'Untitled review'}" permanently? This cannot be undone.`)
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 review dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="review-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
- <div class="flex gap-4 items-start">
14
- <span class="flex flex-shrink-0 items-center justify-center h-10 w-10 text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-950/40 rounded-full">
15
- <span class="h-5 w-5 i-hugeicons-waste"></span>
16
- </span>
17
- <div>
18
- <h2 id="review-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete review</h2>
19
- <p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Delete "{{ review()?.title || 'Untitled review' }}" permanently? This cannot be undone.</p>
20
- </div>
21
- </div>
22
- <footer class="flex gap-2 justify-end mt-6">
23
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
24
- <Button variant="danger" :loading="busy()" @click="emit('confirm')">Delete review</Button>
25
- </footer>
26
- </section>
27
- </div>
11
+ <ConfirmDialog
12
+ :isOpen="open()"
13
+ title="Delete review"
14
+ :description="description()"
15
+ confirmLabel="Delete review"
16
+ :busy="busy()"
17
+ @close="emit('close')"
18
+ @confirm="emit('confirm')"
19
+ />
@@ -49,18 +49,19 @@ function errorMessages(): string[] {
49
49
  }
50
50
  </script>
51
51
 
52
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
53
- <button type="button" aria-label="Close edit review dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
54
- <section role="dialog" aria-modal="true" aria-labelledby="review-edit-title" class="overflow-hidden relative max-w-xl w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
55
- <form @submit.prevent="submit">
56
- <div class="p-6">
57
- <h2 id="review-edit-title" class="font-semibold text-gray-900 text-lg dark:text-white">Edit review</h2>
58
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ review()?.customerName }} on {{ review()?.productName }}</p>
59
- <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">
52
+ <Modal
53
+ :isOpen="open()"
54
+ title="Edit review"
55
+ :description="`${review()?.customerName || 'Unknown customer'} on ${review()?.productName || 'Unknown product'}`"
56
+ size="lg"
57
+ @close="emit('close')"
58
+ >
59
+ <form id="review-form" @submit.prevent="submit">
60
+ <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">
60
61
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
61
62
  </ul>
62
63
 
63
- <div class="mt-5 space-y-4">
64
+ <div class="space-y-4">
64
65
  <label class="block">
65
66
  <span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Title</span>
66
67
  <input x-model="title" 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" />
@@ -90,11 +91,11 @@ function errorMessages(): string[] {
90
91
  </label>
91
92
  </div>
92
93
  </div>
93
- </div>
94
- <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">
95
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
96
- <Button type="submit" :loading="busy()" :disabled="!title().trim() || !content().trim()">Save review</Button>
97
- </footer>
98
94
  </form>
99
- </section>
100
- </div>
95
+ <template #footer>
96
+ <div class="flex gap-2 justify-end w-full">
97
+ <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
98
+ <Button type="submit" form="review-form" :loading="busy()" :disabled="!title().trim() || !content().trim()">Save review</Button>
99
+ </div>
100
+ </template>
101
+ </Modal>
@@ -5,19 +5,19 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as TaxRateRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `${record()?.name || 'This tax rate'} will be permanently removed.`)
9
+ const warning = derived(() => record()?.isDefault
10
+ ? 'This rate is marked as default. No replacement is selected automatically.'
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 tax rate dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="tax-rate-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="tax-rate-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete tax rate?</h2>
14
- <p class="mt-2 text-gray-600 text-sm dark:text-neutral-300"><span class="font-medium">{{ record()?.name }}</span> will be permanently removed.</p>
15
- <p :if="record()?.isDefault" class="mt-3 p-3 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 rounded-lg">This rate is marked as default. No replacement is selected automatically.</p>
16
- <footer class="flex gap-2 justify-end mt-6">
17
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
18
- <Button variant="danger" :loading="busy()" @click="emit('confirm')">
19
- Delete tax rate
20
- </Button>
21
- </footer>
22
- </section>
23
- </div>
14
+ <ConfirmDialog
15
+ :isOpen="open()"
16
+ title="Delete tax rate?"
17
+ :description="description()"
18
+ :warning="warning()"
19
+ confirmLabel="Delete tax rate"
20
+ :busy="busy()"
21
+ @close="emit('close')"
22
+ @confirm="emit('confirm')"
23
+ />
@@ -66,17 +66,18 @@ function errorMessages(): string[] {
66
66
  }
67
67
  </script>
68
68
 
69
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
70
- <button type="button" aria-label="Close tax rate dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
71
- <section role="dialog" aria-modal="true" aria-labelledby="tax-rate-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">
72
- <form @submit.prevent="submit">
73
- <div class="p-6">
74
- <h2 id="tax-rate-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit tax rate' : 'Add tax rate' }}</h2>
75
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">All fields follow the TaxRate model validation rules.</p>
76
- <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">
69
+ <Modal
70
+ :isOpen="open()"
71
+ :title="mode() === 'edit' ? 'Edit tax rate' : 'Add tax rate'"
72
+ description="All fields follow the TaxRate model validation rules."
73
+ size="md"
74
+ @close="emit('close')"
75
+ >
76
+ <form id="tax-rate-form" @submit.prevent="submit">
77
+ <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">
77
78
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
78
79
  </ul>
79
- <div class="mt-5 space-y-4">
80
+ <div class="space-y-4">
80
81
  <label class="block">
81
82
  <span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
82
83
  <input x-model="name" type="text" 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" />
@@ -136,13 +137,13 @@ function errorMessages(): string[] {
136
137
  </span>
137
138
  </label>
138
139
  </div>
139
- </div>
140
- <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">
140
+ </form>
141
+ <template #footer>
142
+ <div class="flex gap-2 justify-end w-full">
141
143
  <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
142
- <Button type="submit" :loading="busy()" :disabled="!name().trim() || !type().trim() || !country().trim() || rate() < 0 || rate() > 100">
144
+ <Button type="submit" form="tax-rate-form" :loading="busy()" :disabled="!name().trim() || !type().trim() || !country().trim() || rate() < 0 || rate() > 100">
143
145
  {{ mode() === 'edit' ? 'Save tax rate' : 'Add tax rate' }}
144
146
  </Button>
145
- </footer>
146
- </form>
147
- </section>
148
- </div>
147
+ </div>
148
+ </template>
149
+ </Modal>
@@ -49,7 +49,7 @@ function formatDate(value: string): string {
49
49
  renaming the rate must not change the bill.
50
50
  --}}
51
51
  <span :if="record.code" class="px-2 py-0.5 font-mono text-gray-600 text-xs dark:text-neutral-300 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record.code }}</span>
52
- <span :if="record.exemptible" class="px-2 py-0.5 font-medium text-amber-700 text-xs dark:text-amber-300 bg-amber-50 dark:bg-amber-950/40 rounded-full" title="Not charged to a customer who qualifies for an exemption">Exemptible</span>
52
+ <span :if="record.exemptible" class="px-2 py-0.5 font-medium text-amber-700 text-xs dark:text-amber-300 bg-amber-50 dark:bg-amber-950/40 rounded-full" x-tooltip="Not charged to a customer who qualifies for an exemption" title="Not charged to a customer who qualifies for an exemption">Exemptible</span>
53
53
  </div>
54
54
  <p class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ record.type }}</p>
55
55
  </td>
@@ -5,22 +5,19 @@ const open = useReactiveProp('open', false)
5
5
  const record = useReactiveProp('record', null as ProductUnitRecord | null)
6
6
  const busy = useReactiveProp('busy', false)
7
7
  const emit = defineEmits()
8
+ const description = derived(() => `${record()?.name || 'This product unit'} will be permanently removed.${record()?.productName ? ` The linked product ${record()?.productName} will remain.` : ''}`)
9
+ const warning = derived(() => record()?.isDefault
10
+ ? `This is the current default for ${record()?.type || 'this unit type'}. No replacement is selected automatically.`
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 product unit dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
12
- <section role="dialog" aria-modal="true" aria-labelledby="product-unit-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="product-unit-delete-title" class="font-semibold text-gray-900 text-lg dark:text-white">Delete product unit?</h2>
14
- <p class="mt-2 text-gray-600 text-sm dark:text-neutral-300">
15
- <span class="font-medium">{{ record()?.name }}</span> will be permanently removed.
16
- <span :if="record()?.productName">The linked product {{ record()?.productName }} will remain.</span>
17
- </p>
18
- <p :if="record()?.isDefault" class="mt-3 p-3 text-amber-800 text-sm dark:text-amber-200 bg-amber-50 dark:bg-amber-950/30 rounded-lg">This is the current default for {{ record()?.type }}. No replacement is selected automatically.</p>
19
- <footer class="flex gap-2 justify-end mt-6">
20
- <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
21
- <Button variant="danger" :loading="busy()" @click="emit('confirm')">
22
- Delete product unit
23
- </Button>
24
- </footer>
25
- </section>
26
- </div>
14
+ <ConfirmDialog
15
+ :isOpen="open()"
16
+ title="Delete product unit?"
17
+ :description="description()"
18
+ :warning="warning()"
19
+ confirmLabel="Delete product unit"
20
+ :busy="busy()"
21
+ @close="emit('close')"
22
+ @confirm="emit('confirm')"
23
+ />
@@ -59,17 +59,18 @@ function errorMessages(): string[] {
59
59
  }
60
60
  </script>
61
61
 
62
- <div :if="open()" class="flex fixed inset-0 z-50 items-center justify-center p-4 dashboard-modal-layer">
63
- <button type="button" aria-label="Close product unit dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
64
- <section role="dialog" aria-modal="true" aria-labelledby="product-unit-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">
65
- <form @submit.prevent="submit">
66
- <div class="p-6">
67
- <h2 id="product-unit-dialog-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ mode() === 'edit' ? 'Edit product unit' : 'Add product unit' }}</h2>
68
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Fields and relationships come from the ProductUnit model.</p>
69
- <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">
62
+ <Modal
63
+ :isOpen="open()"
64
+ :title="mode() === 'edit' ? 'Edit product unit' : 'Add product unit'"
65
+ description="Fields and relationships come from the ProductUnit model."
66
+ size="md"
67
+ @close="emit('close')"
68
+ >
69
+ <form id="product-unit-form" @submit.prevent="submit">
70
+ <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">
70
71
  <template :for="message in errorMessages()" :key="message"><li>{{ message }}</li></template>
71
72
  </ul>
72
- <div class="mt-5 space-y-4">
73
+ <div class="space-y-4">
73
74
  <div class="grid gap-4 sm:grid-cols-2">
74
75
  <label class="block">
75
76
  <span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
@@ -110,13 +111,13 @@ function errorMessages(): string[] {
110
111
  </span>
111
112
  </label>
112
113
  </div>
113
- </div>
114
- <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">
114
+ </form>
115
+ <template #footer>
116
+ <div class="flex gap-2 justify-end w-full">
115
117
  <Button variant="secondary" :disabled="busy()" @click="emit('close')">Cancel</Button>
116
- <Button type="submit" :loading="busy()" :disabled="!name().trim() || !abbreviation().trim() || !type().trim()">
118
+ <Button type="submit" form="product-unit-form" :loading="busy()" :disabled="!name().trim() || !abbreviation().trim() || !type().trim()">
117
119
  {{ mode() === 'edit' ? 'Save product unit' : 'Add product unit' }}
118
120
  </Button>
119
- </footer>
120
- </form>
121
- </section>
122
- </div>
121
+ </div>
122
+ </template>
123
+ </Modal>