@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.
- package/ai/skills/stacks-browse/SKILL.md +2 -1
- package/ai/skills/stacks-browse/scripts/browse.ts +20 -2
- package/ai/skills/stacks-dashboard/SKILL.md +4 -0
- package/app/Actions/Auth/LogoutAction.ts +11 -4
- package/app/Actions/Auth/LogoutAllAction.ts +8 -4
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
- package/app/Actions/Dashboard/Models/model-write.ts +5 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Buttons/Counter.stx +1 -1
- package/resources/components/Buttons/ToggleDark.stx +1 -1
- package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
- package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
- package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
- package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
- package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
- package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
- package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
- package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
- package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
- package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
- package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
- package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
- package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
- package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
- package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
- package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
- package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
- package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
- package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
- package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
- package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
- package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
- package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
- package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
- package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
- package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
- package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
- package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
- package/resources/components/Dashboard/GlobalSearch.stx +3 -9
- package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
- package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
- package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
- package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
- package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
- package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
- package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
- package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
- package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
- package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
- package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
- package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
- package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
- package/resources/components/Dashboard/Navbar.stx +5 -5
- package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
- package/resources/components/Dashboard/UI/Button.stx +4 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +4 -0
- package/resources/components/Dashboard/UI/Drawer.stx +114 -0
- package/resources/components/Dashboard/UI/Modal.stx +19 -12
- package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
- package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
- package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
- package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
- package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
- package/resources/components/Docs/TeamMember.stx +16 -9
- package/resources/components/Forum/ForumReplyForm.stx +7 -7
- package/resources/components/Forum/ForumSidebar.stx +10 -6
- package/resources/components/Marketing/Faqs.stx +5 -5
- package/resources/components/Marketing/Feature.stx +11 -6
- package/resources/components/Marketing/Layout.stx +12 -8
- package/resources/components/Marketing/Pricing.stx +11 -5
- package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
- package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
- package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
- package/resources/components/Marketing/Testimonials.stx +8 -5
- package/resources/views/index.stx +4 -4
- package/views/system-tray/index.stx +1 -1
|
@@ -175,17 +175,13 @@
|
|
|
175
175
|
|
|
176
176
|
const selectedUser = state<UserRow | null>(null)
|
|
177
177
|
const selectedUserGuard = state('web')
|
|
178
|
-
const userRolesDraft = state([])
|
|
179
178
|
const savingUserRoles = state(false)
|
|
180
179
|
|
|
181
180
|
async function loadSelectedUserRoles() {
|
|
182
181
|
const user = selectedUser()
|
|
183
182
|
if (!user) return
|
|
184
183
|
const guardName = selectedUserGuard()
|
|
185
|
-
userRolesDraft.set([])
|
|
186
184
|
await rbac.loadUserRoles(user.id, guardName)
|
|
187
|
-
if (selectedUser()?.id === user.id && selectedUserGuard() === guardName)
|
|
188
|
-
userRolesDraft.set([...rbac.userRoleNames(user.id, guardName)])
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
function selectUser(user: UserRow) {
|
|
@@ -199,21 +195,12 @@
|
|
|
199
195
|
loadSelectedUserRoles()
|
|
200
196
|
}
|
|
201
197
|
|
|
202
|
-
function
|
|
203
|
-
const list = userRolesDraft()
|
|
204
|
-
userRolesDraft.set(
|
|
205
|
-
list.includes(roleName)
|
|
206
|
-
? list.filter(n => n !== roleName)
|
|
207
|
-
: [...list, roleName],
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function saveUserRoles() {
|
|
198
|
+
async function saveUserRoles(roleNames: string[]) {
|
|
212
199
|
const user = selectedUser()
|
|
213
200
|
if (!user) return
|
|
214
201
|
savingUserRoles.set(true)
|
|
215
202
|
try {
|
|
216
|
-
await rbac.syncUserRoles(user.id,
|
|
203
|
+
await rbac.syncUserRoles(user.id, roleNames, selectedUserGuard())
|
|
217
204
|
}
|
|
218
205
|
finally {
|
|
219
206
|
savingUserRoles.set(false)
|
|
@@ -222,14 +209,17 @@
|
|
|
222
209
|
|
|
223
210
|
function closeUserDrawer() {
|
|
224
211
|
selectedUser.set(null)
|
|
225
|
-
userRolesDraft.set([])
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function isRoleInDraft(roleName) {
|
|
229
|
-
return userRolesDraft().includes(roleName)
|
|
230
212
|
}
|
|
231
213
|
|
|
232
214
|
const rolesForSelectedGuard = derived(() => roles().filter(role => role.guardName === selectedUserGuard()))
|
|
215
|
+
const selectedUserRoles = derived(() => {
|
|
216
|
+
const user = selectedUser()
|
|
217
|
+
return user ? rbac.userRoleNames(user.id, selectedUserGuard()) : []
|
|
218
|
+
})
|
|
219
|
+
const selectedUserName = derived(() => {
|
|
220
|
+
const user = selectedUser()
|
|
221
|
+
return user?.name || user?.email || (user ? `User #${user.id}` : '')
|
|
222
|
+
})
|
|
233
223
|
const userRolesLoading = derived(() => {
|
|
234
224
|
const user = selectedUser()
|
|
235
225
|
return user ? Boolean(loadingPivots()[`user:${user.id}:${encodeURIComponent(selectedUserGuard())}`]) : false
|
|
@@ -238,31 +228,19 @@
|
|
|
238
228
|
// ─── Role-permission matrix ───────────────────────────────────
|
|
239
229
|
|
|
240
230
|
const selectedRole = state<RoleRow | null>(null)
|
|
241
|
-
const rolePermsDraft = state([])
|
|
242
231
|
const savingRolePerms = state(false)
|
|
243
232
|
|
|
244
233
|
async function selectRole(role: RoleRow) {
|
|
245
234
|
selectedRole.set(role)
|
|
246
|
-
rolePermsDraft.set([])
|
|
247
235
|
await rbac.loadRolePermissions(role.name, role.guardName)
|
|
248
|
-
rolePermsDraft.set([...rbac.rolePermissionNames(role.name, role.guardName)])
|
|
249
236
|
}
|
|
250
237
|
|
|
251
|
-
function
|
|
252
|
-
const list = rolePermsDraft()
|
|
253
|
-
rolePermsDraft.set(
|
|
254
|
-
list.includes(permName)
|
|
255
|
-
? list.filter(n => n !== permName)
|
|
256
|
-
: [...list, permName],
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function saveRolePerms() {
|
|
238
|
+
async function saveRolePerms(permissionNames: string[]) {
|
|
261
239
|
const role = selectedRole()
|
|
262
240
|
if (!role) return
|
|
263
241
|
savingRolePerms.set(true)
|
|
264
242
|
try {
|
|
265
|
-
await rbac.syncRolePermissions(role.name,
|
|
243
|
+
await rbac.syncRolePermissions(role.name, permissionNames, role.guardName)
|
|
266
244
|
}
|
|
267
245
|
finally {
|
|
268
246
|
savingRolePerms.set(false)
|
|
@@ -271,17 +249,20 @@
|
|
|
271
249
|
|
|
272
250
|
function closeRoleDrawer() {
|
|
273
251
|
selectedRole.set(null)
|
|
274
|
-
rolePermsDraft.set([])
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function isPermInDraft(permName) {
|
|
278
|
-
return rolePermsDraft().includes(permName)
|
|
279
252
|
}
|
|
280
253
|
|
|
281
254
|
const permissionsForSelectedRole = derived(() => {
|
|
282
255
|
const role = selectedRole()
|
|
283
256
|
return role ? permissions().filter(permission => permission.guardName === role.guardName) : []
|
|
284
257
|
})
|
|
258
|
+
const selectedRolePermissions = derived(() => {
|
|
259
|
+
const role = selectedRole()
|
|
260
|
+
return role ? rbac.rolePermissionNames(role.name, role.guardName) : []
|
|
261
|
+
})
|
|
262
|
+
const selectedRoleDescription = derived(() => {
|
|
263
|
+
const role = selectedRole()
|
|
264
|
+
return role ? `Guard: ${role.guardName}` : ''
|
|
265
|
+
})
|
|
285
266
|
|
|
286
267
|
const rolePermissionsLoading = derived(() => {
|
|
287
268
|
const role = selectedRole()
|
|
@@ -332,17 +313,6 @@
|
|
|
332
313
|
}
|
|
333
314
|
}
|
|
334
315
|
|
|
335
|
-
function handleKeydown(event: KeyboardEvent) {
|
|
336
|
-
if (event.key !== 'Escape' || pendingDelete())
|
|
337
|
-
return
|
|
338
|
-
if (selectedRole())
|
|
339
|
-
closeRoleDrawer()
|
|
340
|
-
else if (selectedUser())
|
|
341
|
-
closeUserDrawer()
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
useEventListener('keydown', handleKeydown)
|
|
345
|
-
|
|
346
316
|
// ─── Tiny formatters ──────────────────────────────────────────
|
|
347
317
|
|
|
348
318
|
function formatDate(iso) {
|
|
@@ -545,86 +515,34 @@
|
|
|
545
515
|
</div>
|
|
546
516
|
</div>
|
|
547
517
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
<template :for="option in guardOptions">
|
|
564
|
-
<option :value="option.value">{{ option.label }}</option>
|
|
565
|
-
</template>
|
|
566
|
-
</select>
|
|
567
|
-
</div>
|
|
568
|
-
<div class="overflow-y-auto flex-1 px-5 py-4 space-y-2">
|
|
569
|
-
<div :if="userRolesLoading()" class="flex gap-2 items-center py-6 text-sm text-zinc-500">
|
|
570
|
-
<span class="h-4 w-4 animate-spin i-hugeicons-loading-03"></span>
|
|
571
|
-
Loading assignments…
|
|
572
|
-
</div>
|
|
573
|
-
<div :if="!userRolesLoading() && rolesForSelectedGuard().length === 0" class="text-sm text-zinc-500">No roles are defined for this guard.</div>
|
|
574
|
-
<template :for="role in rolesForSelectedGuard">
|
|
575
|
-
<label class="flex gap-2 items-center px-2 py-1.5 hover:bg-zinc-50 dark:hover:bg-neutral-800 rounded-md cursor-pointer">
|
|
576
|
-
<input type="checkbox" :checked="isRoleInDraft(role.name)" @change="toggleUserRoleDraft(role.name)" class="h-4 w-4 text-violet-600 border-zinc-300 rounded dark:border-zinc-700 focus:ring-violet-500" />
|
|
577
|
-
<span class="text-sm text-zinc-900 dark:text-zinc-100">{{ role.name }}</span>
|
|
578
|
-
<span class="text-xs text-zinc-500">{{ role.description || '' }}</span>
|
|
579
|
-
</label>
|
|
580
|
-
</template>
|
|
581
|
-
</div>
|
|
582
|
-
<div class="flex gap-2 items-center justify-end px-5 py-3 border-t border-zinc-200 dark:border-zinc-800">
|
|
583
|
-
<Button variant="secondary" size="sm" :disabled="savingUserRoles()" @click="closeUserDrawer()">Cancel</Button>
|
|
584
|
-
<Button size="sm" :disabled="userRolesLoading()" :loading="savingUserRoles()" @click="saveUserRoles()">
|
|
585
|
-
Save
|
|
586
|
-
</Button>
|
|
587
|
-
</div>
|
|
588
|
-
</aside>
|
|
589
|
-
</div>
|
|
518
|
+
<AssignmentDrawer
|
|
519
|
+
:open="Boolean(selectedUser())"
|
|
520
|
+
title="Assign roles"
|
|
521
|
+
:description="selectedUserName()"
|
|
522
|
+
:options="rolesForSelectedGuard()"
|
|
523
|
+
:selected="selectedUserRoles()"
|
|
524
|
+
:guard="selectedUserGuard()"
|
|
525
|
+
:guardOptions="guardOptions()"
|
|
526
|
+
:loading="userRolesLoading()"
|
|
527
|
+
:busy="savingUserRoles()"
|
|
528
|
+
emptyMessage="No roles are defined for this guard."
|
|
529
|
+
@guard-change="changeUserGuard"
|
|
530
|
+
@close="closeUserDrawer"
|
|
531
|
+
@save="saveUserRoles"
|
|
532
|
+
/>
|
|
590
533
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
<div class="px-5 py-3 text-xs text-zinc-500 border-b border-zinc-200 dark:border-zinc-800">
|
|
604
|
-
Guard: <code class="text-zinc-700 dark:text-zinc-300">{{ selectedRole().guardName }}</code>
|
|
605
|
-
</div>
|
|
606
|
-
<div class="overflow-y-auto flex-1 px-5 py-4 space-y-2">
|
|
607
|
-
<div :if="rolePermissionsLoading()" class="flex gap-2 items-center py-6 text-sm text-zinc-500">
|
|
608
|
-
<span class="h-4 w-4 animate-spin i-hugeicons-loading-03"></span>
|
|
609
|
-
Loading assignments…
|
|
610
|
-
</div>
|
|
611
|
-
<div :if="!rolePermissionsLoading() && permissionsForSelectedRole().length === 0" class="text-sm text-zinc-500">No permissions are defined for this guard.</div>
|
|
612
|
-
<template :for="perm in permissionsForSelectedRole">
|
|
613
|
-
<label class="flex gap-2 items-center px-2 py-1.5 hover:bg-zinc-50 dark:hover:bg-neutral-800 rounded-md cursor-pointer">
|
|
614
|
-
<input type="checkbox" :checked="isPermInDraft(perm.name)" @change="toggleRolePermDraft(perm.name)" class="h-4 w-4 text-violet-600 border-zinc-300 rounded dark:border-zinc-700 focus:ring-violet-500" />
|
|
615
|
-
<span class="text-sm text-zinc-900 dark:text-zinc-100">{{ perm.name }}</span>
|
|
616
|
-
<span class="text-xs text-zinc-500">{{ perm.description || '' }}</span>
|
|
617
|
-
</label>
|
|
618
|
-
</template>
|
|
619
|
-
</div>
|
|
620
|
-
<div class="flex gap-2 items-center justify-end px-5 py-3 border-t border-zinc-200 dark:border-zinc-800">
|
|
621
|
-
<Button variant="secondary" size="sm" :disabled="savingRolePerms()" @click="closeRoleDrawer()">Cancel</Button>
|
|
622
|
-
<Button size="sm" :disabled="rolePermissionsLoading()" :loading="savingRolePerms()" @click="saveRolePerms()">
|
|
623
|
-
Save
|
|
624
|
-
</Button>
|
|
625
|
-
</div>
|
|
626
|
-
</aside>
|
|
627
|
-
</div>
|
|
534
|
+
<AssignmentDrawer
|
|
535
|
+
:open="Boolean(selectedRole())"
|
|
536
|
+
title="Assign permissions"
|
|
537
|
+
:description="selectedRoleDescription()"
|
|
538
|
+
:options="permissionsForSelectedRole()"
|
|
539
|
+
:selected="selectedRolePermissions()"
|
|
540
|
+
:loading="rolePermissionsLoading()"
|
|
541
|
+
:busy="savingRolePerms()"
|
|
542
|
+
emptyMessage="No permissions are defined for this guard."
|
|
543
|
+
@close="closeRoleDrawer"
|
|
544
|
+
@save="saveRolePerms"
|
|
545
|
+
/>
|
|
628
546
|
|
|
629
547
|
<ConfirmDialog
|
|
630
548
|
:isOpen="Boolean(pendingDelete())"
|
|
@@ -66,11 +66,7 @@ function submit(): void {
|
|
|
66
66
|
}
|
|
67
67
|
</script>
|
|
68
68
|
|
|
69
|
-
<
|
|
70
|
-
<button type="button" aria-label="Close campaign delivery dialog" class="absolute inset-0 bg-black/45" @click="emit('close')"></button>
|
|
71
|
-
<section role="dialog" aria-modal="true" aria-labelledby="campaign-delivery-title" class="relative p-6 max-w-lg w-full bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700 shadow-xl">
|
|
72
|
-
<h2 id="campaign-delivery-title" class="font-semibold text-gray-900 text-lg dark:text-white">{{ heading() }}</h2>
|
|
73
|
-
<p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">{{ description() }}</p>
|
|
69
|
+
<Modal :isOpen="open()" :title="heading()" :description="description()" size="md" @close="emit('close')">
|
|
74
70
|
<div class="mt-4 p-3 bg-gray-50 dark:bg-neutral-900/50 border border-gray-200 rounded-lg dark:border-neutral-700">
|
|
75
71
|
<p class="font-medium text-gray-900 text-sm dark:text-white">{{ record()?.name || '' }}</p>
|
|
76
72
|
<p class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ record()?.emailListName || 'Not assigned' }} · {{ record()?.audienceSize.toLocaleString() || '0' }} active recipients</p>
|
|
@@ -81,7 +77,8 @@ function submit(): void {
|
|
|
81
77
|
<span class="block mt-1 text-gray-500 text-xs dark:text-neutral-400">Uses your browser’s local time.</span>
|
|
82
78
|
</label>
|
|
83
79
|
<div :if="error()" role="alert" 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>
|
|
84
|
-
|
|
80
|
+
<template #footer>
|
|
81
|
+
<div class="flex gap-2 justify-end w-full">
|
|
85
82
|
<Button :disabled="busy()" variant="secondary" @click="emit('close')">Close</Button>
|
|
86
83
|
<template :if="mode() === 'cancel'">
|
|
87
84
|
<Button :disabled="busy() || (mode() === 'schedule' && !scheduledAt())" :loading="busy()" variant="danger" @click="submit()">{{ confirmLabel() }}</Button>
|
|
@@ -90,5 +87,5 @@ function submit(): void {
|
|
|
90
87
|
<Button :disabled="busy() || (mode() === 'schedule' && !scheduledAt())" :loading="busy()" @click="submit()">{{ confirmLabel() }}</Button>
|
|
91
88
|
</template>
|
|
92
89
|
</div>
|
|
93
|
-
</
|
|
94
|
-
</
|
|
90
|
+
</template>
|
|
91
|
+
</Modal>
|
|
@@ -106,17 +106,17 @@ function submit(): void {
|
|
|
106
106
|
}
|
|
107
107
|
</script>
|
|
108
108
|
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
<Modal
|
|
110
|
+
:isOpen="open()"
|
|
111
|
+
:title="mode() === 'edit' ? 'Edit campaign' : 'Add campaign'"
|
|
112
|
+
description="Persist campaign metadata through the Campaign model. Email delivery is started separately after review."
|
|
113
|
+
size="xl"
|
|
114
|
+
@close="emit('close')"
|
|
115
|
+
>
|
|
116
|
+
<form id="campaign-form" @submit.prevent="submit">
|
|
117
|
+
<div :if="error()" role="alert" class="mb-5 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>
|
|
116
118
|
|
|
117
|
-
<div
|
|
118
|
-
|
|
119
|
-
<div class="grid gap-4 sm:grid-cols-2 mt-5">
|
|
119
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
120
120
|
<label class="block sm:col-span-2">
|
|
121
121
|
<span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
|
|
122
122
|
<input x-model="name" type="text" minlength="3" maxlength="255" required autocomplete="off" 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" />
|
|
@@ -213,13 +213,13 @@ function submit(): void {
|
|
|
213
213
|
</label>
|
|
214
214
|
</div>
|
|
215
215
|
</div>
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
</form>
|
|
217
|
+
<template #footer>
|
|
218
|
+
<div class="flex gap-2 justify-end w-full">
|
|
218
219
|
<Button :disabled="busy()" variant="secondary" @click="emit('close')">Cancel</Button>
|
|
219
|
-
<Button type="submit" :disabled="busy() || !canSubmit()" :loading="busy()">
|
|
220
|
+
<Button type="submit" form="campaign-form" :disabled="busy() || !canSubmit()" :loading="busy()">
|
|
220
221
|
{{ mode() === 'edit' ? 'Save campaign' : 'Add campaign' }}
|
|
221
222
|
</Button>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</template>
|
|
225
|
+
</Modal>
|
|
@@ -69,17 +69,17 @@ function submit(): void {
|
|
|
69
69
|
}
|
|
70
70
|
</script>
|
|
71
71
|
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
<Modal
|
|
73
|
+
:isOpen="open()"
|
|
74
|
+
:title="mode() === 'edit' ? 'Edit email list' : 'Add email list'"
|
|
75
|
+
description="Settings persist through the EmailList model and its generated API contract."
|
|
76
|
+
size="lg"
|
|
77
|
+
@close="emit('close')"
|
|
78
|
+
>
|
|
79
|
+
<form id="marketing-list-form" @submit.prevent="submit">
|
|
80
|
+
<div :if="error()" role="alert" class="mb-5 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>
|
|
79
81
|
|
|
80
|
-
<div
|
|
81
|
-
|
|
82
|
-
<div class="mt-5 space-y-4">
|
|
82
|
+
<div class="space-y-4">
|
|
83
83
|
<label class="block">
|
|
84
84
|
<span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
|
|
85
85
|
<input x-model="name" type="text" maxlength="100" required autocomplete="off" 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" />
|
|
@@ -117,13 +117,13 @@ function submit(): void {
|
|
|
117
117
|
</label>
|
|
118
118
|
</div>
|
|
119
119
|
</div>
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
</form>
|
|
121
|
+
<template #footer>
|
|
122
|
+
<div class="flex gap-2 justify-end w-full">
|
|
122
123
|
<Button :disabled="busy()" variant="secondary" @click="emit('close')">Cancel</Button>
|
|
123
|
-
<Button type="submit" :disabled="busy() || !name().trim() || !slug().trim()" :loading="busy()">
|
|
124
|
+
<Button type="submit" form="marketing-list-form" :disabled="busy() || !name().trim() || !slug().trim()" :loading="busy()">
|
|
124
125
|
{{ mode() === 'edit' ? 'Save email list' : 'Add email list' }}
|
|
125
126
|
</Button>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
</Modal>
|
|
@@ -52,7 +52,7 @@ function statusClass(status: string): string {
|
|
|
52
52
|
<td class="px-4 py-4">
|
|
53
53
|
<div class="flex gap-2 items-center">
|
|
54
54
|
<p class="font-medium text-gray-900 text-sm dark:text-white">{{ record.name }}</p>
|
|
55
|
-
<span :if="record.countDrift" title="Stored counters differ from membership rows" class="h-4 w-4 text-amber-500 i-hugeicons-alert-02"></span>
|
|
55
|
+
<span :if="record.countDrift" x-tooltip="Stored counters differ from membership rows" title="Stored counters differ from membership rows" class="h-4 w-4 text-amber-500 i-hugeicons-alert-02"></span>
|
|
56
56
|
</div>
|
|
57
57
|
<p class="mt-1 font-mono text-gray-500 text-xs dark:text-neutral-400">{{ record.slug }}</p>
|
|
58
58
|
<p :if="record.description" class="mt-1 max-w-sm text-gray-500 text-xs dark:text-neutral-400">{{ record.description }}</p>
|
|
@@ -52,16 +52,17 @@ function submit(): void {
|
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
54
|
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
<Modal
|
|
56
|
+
:isOpen="open()"
|
|
57
|
+
:title="mode() === 'edit' ? 'Edit social post' : 'Add social post'"
|
|
58
|
+
description="Create or schedule a persisted SocialPost record. Platform delivery remains the responsibility of the configured publisher."
|
|
59
|
+
size="lg"
|
|
60
|
+
@close="emit('close')"
|
|
61
|
+
>
|
|
62
|
+
<form id="social-post-form" @submit.prevent="submit">
|
|
63
|
+
<div :if="error()" role="alert" class="mb-5 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>
|
|
63
64
|
|
|
64
|
-
<div class="
|
|
65
|
+
<div class="space-y-4">
|
|
65
66
|
<label class="block">
|
|
66
67
|
<span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Content</span>
|
|
67
68
|
<textarea x-model="content" rows="5" maxlength="2000" 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"></textarea>
|
|
@@ -109,13 +110,13 @@ function submit(): void {
|
|
|
109
110
|
<input x-model="imageUrl" type="url" maxlength="2000" placeholder="https://example.com/image.jpg" 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" />
|
|
110
111
|
</label>
|
|
111
112
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
</form>
|
|
114
|
+
<template #footer>
|
|
115
|
+
<div class="flex gap-2 justify-end w-full">
|
|
114
116
|
<Button :disabled="busy()" variant="secondary" @click="emit('close')">Cancel</Button>
|
|
115
|
-
<Button type="submit" :disabled="busy() || !content().trim() || (status() === 'scheduled' && !scheduledAt())" :loading="busy()">
|
|
117
|
+
<Button type="submit" form="social-post-form" :disabled="busy() || !content().trim() || (status() === 'scheduled' && !scheduledAt())" :loading="busy()">
|
|
116
118
|
{{ mode() === 'edit' ? 'Save social post' : 'Add social post' }}
|
|
117
119
|
</Button>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</template>
|
|
122
|
+
</Modal>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface EditField {
|
|
3
|
+
name: string
|
|
4
|
+
label: string
|
|
5
|
+
value: string
|
|
6
|
+
readonly: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const open = useReactiveProp('open', false)
|
|
10
|
+
const modelName = useReactiveProp('modelName', 'Record')
|
|
11
|
+
const fields = useReactiveProp('fields', [] as EditField[])
|
|
12
|
+
const busy = useReactiveProp('busy', false)
|
|
13
|
+
const values = state<Record<string, string>>({})
|
|
14
|
+
const emit = defineEmits()
|
|
15
|
+
let hydrationKey = ''
|
|
16
|
+
|
|
17
|
+
effect(() => {
|
|
18
|
+
if (!open()) {
|
|
19
|
+
hydrationKey = ''
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const key = fields().map(field => `${field.name}:${field.value}`).join('|')
|
|
24
|
+
if (key === hydrationKey)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
hydrationKey = key
|
|
28
|
+
values.set(Object.fromEntries(fields().map(field => [field.name, field.value])))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function updateValue(field: string, event: Event): void {
|
|
32
|
+
values.set({ ...values(), [field]: (event.target as HTMLInputElement).value })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function submit(): void {
|
|
36
|
+
emit('submit', Object.fromEntries(
|
|
37
|
+
fields()
|
|
38
|
+
.filter(field => !field.readonly)
|
|
39
|
+
.map(field => [field.name, values()[field.name]]),
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function close(): void {
|
|
44
|
+
if (!busy())
|
|
45
|
+
emit('close')
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<Modal
|
|
50
|
+
:isOpen="open()"
|
|
51
|
+
:title="'Edit ' + modelName()"
|
|
52
|
+
description="Only changed writable fields will be sent through the model API."
|
|
53
|
+
:closable="!busy()"
|
|
54
|
+
@close="close"
|
|
55
|
+
>
|
|
56
|
+
<form id="model-edit-form" class="overflow-y-auto space-y-3 max-h-[65vh]" @submit.prevent="submit">
|
|
57
|
+
<template :for="field in fields()">
|
|
58
|
+
<div class="flex flex-col gap-1.5">
|
|
59
|
+
<label :for="'model-edit-' + field.name" class="font-medium text-[12px] text-neutral-600 dark:text-neutral-300">{{ field.label }}</label>
|
|
60
|
+
<input
|
|
61
|
+
:id="'model-edit-' + field.name"
|
|
62
|
+
:name="field.readonly ? undefined : field.name"
|
|
63
|
+
type="text"
|
|
64
|
+
:value="values()[field.name] ?? field.value"
|
|
65
|
+
:readonly="field.readonly"
|
|
66
|
+
class="px-2.5 py-1.5 text-[13px] text-neutral-900 dark:text-neutral-100 read-only:text-neutral-500 dark:read-only:text-neutral-400 bg-white dark:bg-white/10 read-only:bg-neutral-50 dark:read-only:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
|
67
|
+
@input="updateValue(field.name, $event)"
|
|
68
|
+
>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
</form>
|
|
72
|
+
|
|
73
|
+
<template #footer>
|
|
74
|
+
<div class="flex gap-2 items-center justify-end w-full">
|
|
75
|
+
<Button :disabled="busy()" variant="secondary" @click="close">Cancel</Button>
|
|
76
|
+
<Button type="submit" form="model-edit-form" :loading="busy()">
|
|
77
|
+
{{ busy() ? 'Saving' : 'Save changes' }}
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
</Modal>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
const record = useReactiveProp('record', {} as Record<string, unknown>)
|
|
3
|
+
const modelName = useReactiveProp('modelName', 'Record')
|
|
4
|
+
const canUpdate = useReactiveProp('canUpdate', false)
|
|
5
|
+
const canDestroy = useReactiveProp('canDestroy', false)
|
|
6
|
+
const emit = defineEmits()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="flex gap-1 items-center justify-end">
|
|
10
|
+
<Button
|
|
11
|
+
:if="canUpdate()"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
size="sm"
|
|
14
|
+
iconOnly
|
|
15
|
+
:ariaLabel="'Edit ' + modelName() + ' ' + record().id"
|
|
16
|
+
@click="emit('edit', record())"
|
|
17
|
+
>
|
|
18
|
+
<span class="h-4 w-4 i-hugeicons-edit-02"></span>
|
|
19
|
+
</Button>
|
|
20
|
+
<Button
|
|
21
|
+
:if="canDestroy()"
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="sm"
|
|
24
|
+
iconOnly
|
|
25
|
+
:ariaLabel="'Delete ' + modelName() + ' ' + record().id"
|
|
26
|
+
@click="emit('delete', record())"
|
|
27
|
+
>
|
|
28
|
+
<span class="h-4 w-4 text-red-500 i-hugeicons-delete-02"></span>
|
|
29
|
+
</Button>
|
|
30
|
+
</div>
|