@stacksjs/defaults 0.70.207 → 0.70.208

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 (408) hide show
  1. package/TODO.md +21 -0
  2. package/ai/AGENTS.md +79 -0
  3. package/ai/README.md +45 -0
  4. package/ai/claude/launch.json +12 -0
  5. package/ai/skills/stacks-actions/SKILL.md +128 -0
  6. package/ai/skills/stacks-ai/SKILL.md +276 -0
  7. package/ai/skills/stacks-alias/SKILL.md +94 -0
  8. package/ai/skills/stacks-analytics/SKILL.md +91 -0
  9. package/ai/skills/stacks-api/SKILL.md +738 -0
  10. package/ai/skills/stacks-arrays/SKILL.md +277 -0
  11. package/ai/skills/stacks-auth/SKILL.md +422 -0
  12. package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
  13. package/ai/skills/stacks-brandkit/SKILL.md +811 -0
  14. package/ai/skills/stacks-browse/SKILL.md +127 -0
  15. package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
  16. package/ai/skills/stacks-browser/SKILL.md +145 -0
  17. package/ai/skills/stacks-buddy/SKILL.md +676 -0
  18. package/ai/skills/stacks-build/SKILL.md +163 -0
  19. package/ai/skills/stacks-cache/SKILL.md +125 -0
  20. package/ai/skills/stacks-calendar/SKILL.md +113 -0
  21. package/ai/skills/stacks-chat/SKILL.md +371 -0
  22. package/ai/skills/stacks-cli/SKILL.md +112 -0
  23. package/ai/skills/stacks-cloud/SKILL.md +192 -0
  24. package/ai/skills/stacks-cms/SKILL.md +217 -0
  25. package/ai/skills/stacks-collections/SKILL.md +82 -0
  26. package/ai/skills/stacks-commerce/SKILL.md +114 -0
  27. package/ai/skills/stacks-composables/SKILL.md +161 -0
  28. package/ai/skills/stacks-config/SKILL.md +144 -0
  29. package/ai/skills/stacks-configuration/SKILL.md +154 -0
  30. package/ai/skills/stacks-cron/SKILL.md +123 -0
  31. package/ai/skills/stacks-crosswind/SKILL.md +190 -0
  32. package/ai/skills/stacks-dashboard/SKILL.md +173 -0
  33. package/ai/skills/stacks-database/SKILL.md +286 -0
  34. package/ai/skills/stacks-datetime/SKILL.md +269 -0
  35. package/ai/skills/stacks-dependencies/SKILL.md +111 -0
  36. package/ai/skills/stacks-deploy/SKILL.md +102 -0
  37. package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
  38. package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
  39. package/ai/skills/stacks-design-output/SKILL.md +55 -0
  40. package/ai/skills/stacks-design-soft/SKILL.md +156 -0
  41. package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
  42. package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
  43. package/ai/skills/stacks-desktop/SKILL.md +78 -0
  44. package/ai/skills/stacks-development/SKILL.md +298 -0
  45. package/ai/skills/stacks-dns/SKILL.md +149 -0
  46. package/ai/skills/stacks-docs/SKILL.md +109 -0
  47. package/ai/skills/stacks-email/SKILL.md +236 -0
  48. package/ai/skills/stacks-enums/SKILL.md +100 -0
  49. package/ai/skills/stacks-env/SKILL.md +181 -0
  50. package/ai/skills/stacks-error-handling/SKILL.md +137 -0
  51. package/ai/skills/stacks-events/SKILL.md +228 -0
  52. package/ai/skills/stacks-faker/SKILL.md +158 -0
  53. package/ai/skills/stacks-git/SKILL.md +158 -0
  54. package/ai/skills/stacks-guard/SKILL.md +98 -0
  55. package/ai/skills/stacks-health/SKILL.md +75 -0
  56. package/ai/skills/stacks-http/SKILL.md +316 -0
  57. package/ai/skills/stacks-i18n/SKILL.md +198 -0
  58. package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
  59. package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
  60. package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
  61. package/ai/skills/stacks-investigate/SKILL.md +119 -0
  62. package/ai/skills/stacks-jobs/SKILL.md +136 -0
  63. package/ai/skills/stacks-lint/SKILL.md +184 -0
  64. package/ai/skills/stacks-listeners/SKILL.md +99 -0
  65. package/ai/skills/stacks-logging/SKILL.md +78 -0
  66. package/ai/skills/stacks-mail/SKILL.md +120 -0
  67. package/ai/skills/stacks-middleware/SKILL.md +249 -0
  68. package/ai/skills/stacks-migrations/SKILL.md +105 -0
  69. package/ai/skills/stacks-models/SKILL.md +267 -0
  70. package/ai/skills/stacks-new-feature/SKILL.md +206 -0
  71. package/ai/skills/stacks-notifications/SKILL.md +210 -0
  72. package/ai/skills/stacks-objects/SKILL.md +205 -0
  73. package/ai/skills/stacks-office-hours/SKILL.md +134 -0
  74. package/ai/skills/stacks-orm/SKILL.md +342 -0
  75. package/ai/skills/stacks-path/SKILL.md +118 -0
  76. package/ai/skills/stacks-payments/SKILL.md +373 -0
  77. package/ai/skills/stacks-plan-review/SKILL.md +152 -0
  78. package/ai/skills/stacks-plugins/SKILL.md +149 -0
  79. package/ai/skills/stacks-push/SKILL.md +376 -0
  80. package/ai/skills/stacks-query-builder/SKILL.md +128 -0
  81. package/ai/skills/stacks-queue/SKILL.md +559 -0
  82. package/ai/skills/stacks-realtime/SKILL.md +442 -0
  83. package/ai/skills/stacks-redesign/SKILL.md +182 -0
  84. package/ai/skills/stacks-registry/SKILL.md +58 -0
  85. package/ai/skills/stacks-repl/SKILL.md +80 -0
  86. package/ai/skills/stacks-retro/SKILL.md +122 -0
  87. package/ai/skills/stacks-review/SKILL.md +135 -0
  88. package/ai/skills/stacks-router/SKILL.md +231 -0
  89. package/ai/skills/stacks-routes/SKILL.md +105 -0
  90. package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
  91. package/ai/skills/stacks-scheduler/SKILL.md +363 -0
  92. package/ai/skills/stacks-search-engine/SKILL.md +112 -0
  93. package/ai/skills/stacks-security/SKILL.md +129 -0
  94. package/ai/skills/stacks-security-audit/SKILL.md +128 -0
  95. package/ai/skills/stacks-server/SKILL.md +458 -0
  96. package/ai/skills/stacks-shell/SKILL.md +62 -0
  97. package/ai/skills/stacks-slug/SKILL.md +87 -0
  98. package/ai/skills/stacks-sms/SKILL.md +385 -0
  99. package/ai/skills/stacks-socials/SKILL.md +303 -0
  100. package/ai/skills/stacks-storage/SKILL.md +576 -0
  101. package/ai/skills/stacks-strings/SKILL.md +416 -0
  102. package/ai/skills/stacks-stx/SKILL.md +176 -0
  103. package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
  104. package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
  105. package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
  106. package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
  107. package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
  108. package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
  109. package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
  110. package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
  111. package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
  112. package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
  113. package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
  114. package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
  115. package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
  116. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
  117. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
  118. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
  119. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
  120. package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
  121. package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
  122. package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
  123. package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
  124. package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
  125. package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
  126. package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
  127. package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
  128. package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
  129. package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
  130. package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
  131. package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
  132. package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
  133. package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
  134. package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
  135. package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
  136. package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
  137. package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
  138. package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
  139. package/ai/skills/stacks-testing/SKILL.md +129 -0
  140. package/ai/skills/stacks-tunnel/SKILL.md +125 -0
  141. package/ai/skills/stacks-types/SKILL.md +178 -0
  142. package/ai/skills/stacks-ui/SKILL.md +246 -0
  143. package/ai/skills/stacks-utils/SKILL.md +444 -0
  144. package/ai/skills/stacks-validation/SKILL.md +371 -0
  145. package/ai/skills/stacks-whois/SKILL.md +315 -0
  146. package/assets/world-map-data.ts +121 -0
  147. package/bootstrap.ts +114 -0
  148. package/docs/api-examples.md +46 -0
  149. package/docs/index.md +24 -0
  150. package/docs/markdown-examples.md +87 -0
  151. package/functions/analytics.ts +97 -0
  152. package/functions/api.ts +143 -0
  153. package/functions/auth.ts +208 -0
  154. package/functions/billing/payments.ts +149 -0
  155. package/functions/blog.ts +120 -0
  156. package/functions/cms/categorizables.ts +107 -0
  157. package/functions/cms/pages.ts +108 -0
  158. package/functions/cms/posts.ts +100 -0
  159. package/functions/cms/taggables.ts +102 -0
  160. package/functions/commerce/coupons.ts +128 -0
  161. package/functions/commerce/customers.ts +128 -0
  162. package/functions/commerce/gift-cards.ts +119 -0
  163. package/functions/commerce/orders.ts +112 -0
  164. package/functions/commerce/payments.ts +119 -0
  165. package/functions/commerce/products/categories.ts +123 -0
  166. package/functions/commerce/products/manufacturers.ts +123 -0
  167. package/functions/commerce/products/products.ts +123 -0
  168. package/functions/commerce/products/reviews.ts +123 -0
  169. package/functions/commerce/products/units.ts +123 -0
  170. package/functions/commerce/products/variants.ts +123 -0
  171. package/functions/commerce/shippings/delivery-routes.ts +123 -0
  172. package/functions/commerce/shippings/digital-deliveries.ts +119 -0
  173. package/functions/commerce/shippings/drivers.ts +123 -0
  174. package/functions/commerce/shippings/license-keys.ts +123 -0
  175. package/functions/commerce/shippings/shipping-methods.ts +123 -0
  176. package/functions/commerce/shippings/shipping-rates.ts +119 -0
  177. package/functions/commerce/shippings/shipping-zones.ts +119 -0
  178. package/functions/commerce/taxes.ts +119 -0
  179. package/functions/commerce/waitlist/products.ts +123 -0
  180. package/functions/commerce/waitlist/restaurants.ts +123 -0
  181. package/functions/craft.ts +107 -0
  182. package/functions/dashboard.ts +153 -0
  183. package/functions/inbox.ts +95 -0
  184. package/functions/jobs.ts +86 -0
  185. package/functions/monitoring/errors.ts +295 -0
  186. package/functions/queue.ts +111 -0
  187. package/functions/realtime/websockets.ts +96 -0
  188. package/functions/realtime.ts +52 -0
  189. package/functions/releases.ts +49 -0
  190. package/functions/search.ts +126 -0
  191. package/functions/toasts.ts +95 -0
  192. package/functions/uploads.ts +216 -0
  193. package/ide/.gitignore +4 -0
  194. package/ide/cursor/rules/auto-imports.mdc +574 -0
  195. package/ide/cursor/rules/code-style.mdc +12 -0
  196. package/ide/cursor/rules/documentation.mdc +9 -0
  197. package/ide/cursor/rules/error-handling-validation.mdc +11 -0
  198. package/ide/cursor/rules/key-conventions.mdc +11 -0
  199. package/ide/cursor/rules/project-structure.mdc +169 -0
  200. package/ide/cursor/rules/readme.mdc +318 -0
  201. package/ide/cursor/rules/syntax-formatting.mdc +9 -0
  202. package/ide/cursor/rules/testing.mdc +10 -0
  203. package/ide/cursor/rules/typescript.mdc +9 -0
  204. package/ide/cursor/rules/ui-styling.mdc +0 -0
  205. package/ide/dictionary.txt +397 -0
  206. package/ide/jetbrains/.fleet/settings.json +4 -0
  207. package/ide/jetbrains/.idea/.gitignore +5 -0
  208. package/ide/jetbrains/.idea/.name +1 -0
  209. package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
  210. package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
  211. package/ide/jetbrains/.idea/modules.xml +8 -0
  212. package/ide/jetbrains/.idea/stacks.iml +22 -0
  213. package/ide/jetbrains/.idea/vcs.xml +6 -0
  214. package/ide/jetbrains/.idea/webResources.xml +14 -0
  215. package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
  216. package/ide/jetbrains/installed.txt +4 -0
  217. package/ide/jetbrains/options/github-copilot.xml +5 -0
  218. package/ide/jetbrains/options/nodejs.xml +7 -0
  219. package/ide/jetbrains/options/templates.xml +124 -0
  220. package/ide/jetbrains/templates/JavaScript.xml +6 -0
  221. package/ide/jetbrains/templates/Stacks.xml +8 -0
  222. package/ide/vscode/.vscode/README.md +23 -0
  223. package/ide/vscode/.vscode/extensions.json +5 -0
  224. package/ide/vscode/.vscode/settings.json +176 -0
  225. package/ide/vscode/.vscode/stacks.code-snippets +181 -0
  226. package/ide/vscode/LICENSE.md +21 -0
  227. package/ide/vscode/README.md +36 -0
  228. package/ide/vscode/logo.png +0 -0
  229. package/ide/vscode/package.json +79 -0
  230. package/ide/zed/.zed/settings.json +139 -0
  231. package/lang/README.md +28 -0
  232. package/lang/de.yml +14 -0
  233. package/lang/en.yml +15 -0
  234. package/package.json +19 -3
  235. package/project/bootstrap +161 -0
  236. package/project/buddy +175 -0
  237. package/project/storage/framework/tsconfig.app.json +50 -0
  238. package/project/storage/framework/tsconfig.base.json +58 -0
  239. package/routes/core.ts +78 -0
  240. package/routes/dashboard-api.ts +233 -0
  241. package/routes/dashboard.ts +739 -0
  242. package/routes/email.ts +129 -0
  243. package/stores/[name].stx +16 -0
  244. package/stores/git.ts +71 -0
  245. package/stores/payment.ts +474 -0
  246. package/stores/queue.ts +43 -0
  247. package/stores/user.ts +29 -0
  248. package/stx-components-plugin.ts +25 -0
  249. package/styles/design-tokens.css +597 -0
  250. package/types/dashboard.ts +70 -0
  251. package/types/defaults.ts +438 -0
  252. package/vcs/github/CODE_OF_CONDUCT.md +83 -0
  253. package/vcs/github/CONTRIBUTING.md +187 -0
  254. package/vcs/github/EXPLANATIONS.md +41 -0
  255. package/vcs/github/FUNDING.yml +1 -0
  256. package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
  257. package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
  258. package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
  259. package/vcs/github/SECURITY.md +70 -0
  260. package/vcs/github/renovate.json +5 -0
  261. package/vcs/github/stale.yml +14 -0
  262. package/vcs/github/workflows/README.md +16 -0
  263. package/vcs/github/workflows/ci.yml +114 -0
  264. package/vcs/github/workflows/export-size.yml +25 -0
  265. package/vcs/github/workflows/labeler.yml +13 -0
  266. package/vcs/github/workflows/release.yml +219 -0
  267. package/views/[...all].stx +8 -0
  268. package/views/about.md +3 -0
  269. package/views/auth/login.stx +1 -0
  270. package/views/auth/signup.stx +85 -0
  271. package/views/dashboard/.discovered-models.json +542 -0
  272. package/views/dashboard/AUDIT.md +904 -0
  273. package/views/dashboard/[...all].stx +89 -0
  274. package/views/dashboard/access-tokens/index.stx +177 -0
  275. package/views/dashboard/actions/index.stx +129 -0
  276. package/views/dashboard/analytics/blog/index.stx +356 -0
  277. package/views/dashboard/analytics/browsers/index.stx +263 -0
  278. package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
  279. package/views/dashboard/analytics/commerce/web/index.stx +379 -0
  280. package/views/dashboard/analytics/countries/index.stx +289 -0
  281. package/views/dashboard/analytics/devices/index.stx +305 -0
  282. package/views/dashboard/analytics/events/index.stx +536 -0
  283. package/views/dashboard/analytics/index.stx +88 -0
  284. package/views/dashboard/analytics/marketing/index.stx +315 -0
  285. package/views/dashboard/analytics/pages/index.stx +271 -0
  286. package/views/dashboard/analytics/referrers/index.stx +319 -0
  287. package/views/dashboard/analytics/web/index.stx +356 -0
  288. package/views/dashboard/buddy/index.stx +198 -0
  289. package/views/dashboard/ci/index.stx +462 -0
  290. package/views/dashboard/cloud/index.stx +452 -0
  291. package/views/dashboard/commands/index.stx +295 -0
  292. package/views/dashboard/commerce/categories/index.stx +406 -0
  293. package/views/dashboard/commerce/coupons/index.stx +485 -0
  294. package/views/dashboard/commerce/customers/index.stx +410 -0
  295. package/views/dashboard/commerce/dashboard/index.stx +287 -0
  296. package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
  297. package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
  298. package/views/dashboard/commerce/delivery/drivers.stx +397 -0
  299. package/views/dashboard/commerce/delivery/index.stx +18 -0
  300. package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
  301. package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
  302. package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
  303. package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
  304. package/views/dashboard/commerce/delivery/tabs.ts +25 -0
  305. package/views/dashboard/commerce/gift-cards/index.stx +561 -0
  306. package/views/dashboard/commerce/manufacturers/index.stx +308 -0
  307. package/views/dashboard/commerce/orders/index.stx +247 -0
  308. package/views/dashboard/commerce/payments/index.stx +535 -0
  309. package/views/dashboard/commerce/pos/index.stx +653 -0
  310. package/views/dashboard/commerce/printers/devices/index.stx +273 -0
  311. package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
  312. package/views/dashboard/commerce/products/detail.stx +563 -0
  313. package/views/dashboard/commerce/products/index.stx +370 -0
  314. package/views/dashboard/commerce/reviews/index.stx +656 -0
  315. package/views/dashboard/commerce/reviews/index.ts +0 -0
  316. package/views/dashboard/commerce/taxes/index.stx +377 -0
  317. package/views/dashboard/commerce/units/index.stx +317 -0
  318. package/views/dashboard/commerce/variants/index.stx +319 -0
  319. package/views/dashboard/commerce/waitlist/products.stx +942 -0
  320. package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
  321. package/views/dashboard/components/index.stx +330 -0
  322. package/views/dashboard/composables/index.ts +19 -0
  323. package/views/dashboard/composables/useChart.ts +77 -0
  324. package/views/dashboard/composables/useNavigation.ts +99 -0
  325. package/views/dashboard/composables/useRole.ts +58 -0
  326. package/views/dashboard/composables/useTheme.ts +74 -0
  327. package/views/dashboard/content/authors/index.stx +171 -0
  328. package/views/dashboard/content/blog/index.stx +493 -0
  329. package/views/dashboard/content/categories/index.stx +169 -0
  330. package/views/dashboard/content/comments/index.stx +189 -0
  331. package/views/dashboard/content/dashboard.stx +383 -0
  332. package/views/dashboard/content/files/index.stx +706 -0
  333. package/views/dashboard/content/pages/index.stx +174 -0
  334. package/views/dashboard/content/posts/index.stx +269 -0
  335. package/views/dashboard/content/seo/index.stx +240 -0
  336. package/views/dashboard/content/tags/index.stx +167 -0
  337. package/views/dashboard/data/activity/index.stx +261 -0
  338. package/views/dashboard/data/dashboard/index.stx +116 -0
  339. package/views/dashboard/data/subscribers.stx +270 -0
  340. package/views/dashboard/data/teams.stx +275 -0
  341. package/views/dashboard/data/users.stx +279 -0
  342. package/views/dashboard/dependencies/index.stx +178 -0
  343. package/views/dashboard/deployments/[id].stx +234 -0
  344. package/views/dashboard/deployments/index.stx +9 -0
  345. package/views/dashboard/dns/index.stx +163 -0
  346. package/views/dashboard/environment/index.stx +283 -0
  347. package/views/dashboard/errors/index.stx +274 -0
  348. package/views/dashboard/forgot-password.stx +57 -0
  349. package/views/dashboard/functions/index.stx +159 -0
  350. package/views/dashboard/health/index.stx +248 -0
  351. package/views/dashboard/inbox/activity.stx +350 -0
  352. package/views/dashboard/inbox/index.stx +452 -0
  353. package/views/dashboard/inbox/settings.stx +290 -0
  354. package/views/dashboard/index.stx +252 -0
  355. package/views/dashboard/insights/index.stx +529 -0
  356. package/views/dashboard/jobs/[id].stx +430 -0
  357. package/views/dashboard/jobs/history.stx +222 -0
  358. package/views/dashboard/jobs/index.stx +214 -0
  359. package/views/dashboard/kanban/[id].stx +899 -0
  360. package/views/dashboard/kanban/index.stx +260 -0
  361. package/views/dashboard/layouts/default.stx +565 -0
  362. package/views/dashboard/layouts/guest.stx +10 -0
  363. package/views/dashboard/login.stx +48 -0
  364. package/views/dashboard/logs/index.stx +540 -0
  365. package/views/dashboard/mailboxes/index.stx +149 -0
  366. package/views/dashboard/management/permissions/index.stx +490 -0
  367. package/views/dashboard/marketing/campaigns/index.stx +604 -0
  368. package/views/dashboard/marketing/lists/index.stx +514 -0
  369. package/views/dashboard/marketing/reviews/index.stx +475 -0
  370. package/views/dashboard/marketing/social-posts/index.stx +560 -0
  371. package/views/dashboard/models/[model].stx +195 -0
  372. package/views/dashboard/models/index.stx +159 -0
  373. package/views/dashboard/monitoring/errors/index.stx +415 -0
  374. package/views/dashboard/notifications/dashboard.stx +383 -0
  375. package/views/dashboard/notifications/email.stx +336 -0
  376. package/views/dashboard/notifications/history.stx +373 -0
  377. package/views/dashboard/notifications/sms.stx +332 -0
  378. package/views/dashboard/packages/index.stx +195 -0
  379. package/views/dashboard/queries/[id].stx +381 -0
  380. package/views/dashboard/queries/history.stx +227 -0
  381. package/views/dashboard/queries/index.stx +335 -0
  382. package/views/dashboard/queries/slow.stx +318 -0
  383. package/views/dashboard/queue/index.stx +427 -0
  384. package/views/dashboard/realtime/index.stx +429 -0
  385. package/views/dashboard/register.stx +46 -0
  386. package/views/dashboard/releases/index.stx +389 -0
  387. package/views/dashboard/requests/index.stx +462 -0
  388. package/views/dashboard/serverless/index.stx +475 -0
  389. package/views/dashboard/servers/[id].stx +299 -0
  390. package/views/dashboard/servers/index.stx +483 -0
  391. package/views/dashboard/settings/billing.stx +50 -0
  392. package/views/dashboard/settings/index.stx +256 -0
  393. package/views/dashboard/settings/mail.stx +252 -0
  394. package/views/dashboard/stores/auth.ts +123 -0
  395. package/views/dashboard/stores/ci.ts +342 -0
  396. package/views/dashboard/stores/index.ts +129 -0
  397. package/views/dashboard/stores/kanban.ts +723 -0
  398. package/views/dashboard/stores/rbac.ts +350 -0
  399. package/views/dashboard/stx.config.ts +34 -0
  400. package/views/dashboard/teams/[id].stx +170 -0
  401. package/views/forum/category.stx +92 -0
  402. package/views/forum/index.stx +209 -0
  403. package/views/forum/new-topic.stx +113 -0
  404. package/views/forum/profile.stx +1 -0
  405. package/views/forum/topic.stx +143 -0
  406. package/views/hello-world/[name].stx +47 -0
  407. package/views/index.stx +21 -0
  408. package/views/system-tray/index.stx +147 -0
@@ -0,0 +1,149 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Emails')
5
+ <div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
6
+ <div class="px-4 sm:px-6 lg:px-8">
7
+ <div class="mb-8 bg-white dark:bg-neutral-700 rounded-lg shadow">
8
+ <div class="p-6">
9
+ <div class="flex items-center justify-between mb-6">
10
+ <div>
11
+ <h3 class="font-medium text-base text-gray-900 dark:text-neutral-100">Email Activity</h3>
12
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Received, sent, and spam email volume</p>
13
+ </div>
14
+ <select x-model="timeRange" class="pl-3 pr-8 py-1.5 h-9 text-gray-900 text-sm dark:text-neutral-100 bg-gray-50 dark:bg-neutral-600 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
15
+ <option value="day">Last 24 Hours</option>
16
+ <option value="week">Last 7 Days</option>
17
+ <option value="month">Last 30 Days</option>
18
+ <option value="year">Last Year</option>
19
+ </select>
20
+ </div>
21
+ <div class="relative h-[300px]">
22
+ <div :show="isLoading" class="flex absolute inset-0 z-10 items-center justify-center bg-opacity-75 bg-white dark:bg-neutral-700 dark:bg-opacity-75">
23
+ <div class="h-8 w-8 border-b-2 border-blue-500 rounded-full animate-spin"></div>
24
+ </div>
25
+ <canvas id="mailboxes_activity_chart"></canvas>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="sm:flex sm:items-center mb-8">
31
+ <div class="sm:flex-auto">
32
+ <h1 class="font-semibold leading-6 text-base text-gray-900">Mailbox</h1>
33
+ <p class="mt-2 text-gray-700 text-sm dark:text-neutral-400">A list of all your project's email inboxes.</p>
34
+ </div>
35
+ <div class="sm:flex-none mt-4 sm:ml-16 sm:mt-0">
36
+ <button type="button" class="block px-3 py-2 font-semibold text-center text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 shadow-sm">Add Mailbox</button>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="flow-root mt-8">
41
+ <div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
42
+ <div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
43
+ <div class="overflow-hidden ring-1 ring-black ring-opacity-5 sm:rounded-lg shadow">
44
+ <table class="min-w-full divide-gray-300 divide-y">
45
+ <thead class="bg-gray-50 dark:bg-neutral-700/50">
46
+ <tr>
47
+ <th scope="col" class="pl-4 pr-3 py-3.5 sm:pl-6 font-semibold text-gray-900 text-left text-sm">Inbox</th>
48
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm">Name</th>
49
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm">Created At</th>
50
+ <th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Edit</span></th>
51
+ </tr>
52
+ </thead>
53
+
54
+ <tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y">
55
+ <tr>
56
+ <td class="pl-4 pr-3 py-4 sm:pl-6 font-medium text-gray-900 text-sm whitespace-nowrap">chris@stacksjs.com</td>
57
+ <td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap">Chris Breuer</td>
58
+ <td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap">2023-08-01</td>
59
+ <td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
60
+ <a href="#" class="text-blue-600 hover:text-blue-900">Edit<span class="sr-only">, Email Inbox</span></a>
61
+ </td>
62
+ </tr>
63
+
64
+ <tr>
65
+ <td class="pl-4 pr-3 py-4 sm:pl-6 font-medium text-gray-900 text-sm whitespace-nowrap">blake@stacksjs.com</td>
66
+ <td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap">Blake</td>
67
+ <td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap">2024-01-11</td>
68
+ <td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
69
+ <a href="#" class="text-blue-600 hover:text-blue-900">Edit<span class="sr-only">, Email Inbox</span></a>
70
+ </td>
71
+ </tr>
72
+ </tbody>
73
+ </table>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <script client>
82
+
83
+ interface ChartLike { destroy: () => void }
84
+ type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
85
+
86
+ const timeRange = state('day')
87
+ const isLoading = state(false)
88
+
89
+ let chartHandle: { destroy: () => void } | null = null
90
+
91
+ const chartOptions = {
92
+ responsive: true,
93
+ maintainAspectRatio: false,
94
+ scales: {
95
+ y: {
96
+ beginAtZero: true,
97
+ grid: { color: 'rgba(200, 200, 200, 0.1)' },
98
+ ticks: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" } },
99
+ },
100
+ x: {
101
+ grid: { display: false },
102
+ ticks: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" } },
103
+ },
104
+ },
105
+ plugins: {
106
+ legend: {
107
+ display: true,
108
+ position: 'top',
109
+ align: 'end',
110
+ labels: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" }, boxWidth: 12, padding: 15 },
111
+ },
112
+ tooltip: { mode: 'index', intersect: false },
113
+ },
114
+ }
115
+
116
+ function getEmailActivityData() {
117
+ const labels = Array.from({ length: 24 }, (_, i) => `${i}:00`)
118
+ return {
119
+ labels,
120
+ datasets: [
121
+ { label: 'Received', data: Array.from({ length: 24 }, () => Math.floor(Math.random() * 50) + 20), borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true },
122
+ { label: 'Sent', data: Array.from({ length: 24 }, () => Math.floor(Math.random() * 30) + 10), borderColor: 'rgb(16, 185, 129)', backgroundColor: 'rgba(16, 185, 129, 0.1)', fill: true },
123
+ { label: 'Spam', data: Array.from({ length: 24 }, () => Math.floor(Math.random() * 10)), borderColor: 'rgb(239, 68, 68)', backgroundColor: 'rgba(239, 68, 68, 0.1)', fill: true },
124
+ ],
125
+ }
126
+ }
127
+
128
+ onMount(async () => {
129
+ isLoading.set(true)
130
+ const { Chart, registerables } = await import('@stacksjs/charts')
131
+ Chart.register(...registerables)
132
+ chartHandle = useChart({
133
+ Chart: Chart as unknown as ChartCtor,
134
+ id: 'mailboxes_activity_chart',
135
+ type: 'line',
136
+ data: getEmailActivityData(),
137
+ options: chartOptions,
138
+ })
139
+ await new Promise(resolve => setTimeout(resolve, 500))
140
+ isLoading.set(false)
141
+ })
142
+
143
+ onDestroy(() => {
144
+ chartHandle?.destroy()
145
+ chartHandle = null
146
+ })
147
+ </script>
148
+
149
+ @endsection
@@ -0,0 +1,490 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <script client>
5
+ // RBAC management surface (stacksjs/stacks#1845).
6
+ //
7
+ // Lets an admin manage roles, permissions, per-user role attachments,
8
+ // and the role/permission matrix against the live `user_roles` /
9
+ // `role_permissions` / `user_permissions` pivots. All persistence
10
+ // flows through the `/api/dashboard/rbac/*` endpoints which delegate
11
+ // to `@stacksjs/auth`'s `Rbac` facade — no direct DB writes from the
12
+ // page.
13
+ //
14
+ // Follows #1838 conventions:
15
+ // - No `<script server>` and no vanilla DOM reads.
16
+ // - All state in the rbacStore; the page is pure presentation.
17
+ // - Mutations are optimistic with rollback on failure (store handles
18
+ // the snapshot/rollback dance).
19
+ import { useStore } from '@stacksjs/stx'
20
+ import { useRole } from '../../composables/useRole'
21
+
22
+ const rbac = useStore('rbac')
23
+ // Admin-only surface. Per `useRole.ts`, `isAdmin()` returns true for
24
+ // any user with the `admin` role *or* an unauthenticated viewer on
25
+ // the localhost dev dashboard — same permissive-dev defaults the rest
26
+ // of the management surface uses.
27
+ const { isAdmin } = useRole()
28
+ const canManage = derived(() => isAdmin())
29
+
30
+ // ─── Lifted store signals (for template auto-unwrap) ──────────
31
+
32
+ const loading = derived(() => rbac.loading())
33
+ const error = derived(() => rbac.error())
34
+ const roles = derived(() => rbac.roles())
35
+ const permissions = derived(() => rbac.permissions())
36
+ const users = derived(() => rbac.users())
37
+
38
+ // ─── Tab state ────────────────────────────────────────────────
39
+
40
+ const activeTab = state('roles')
41
+
42
+ function tabClass(id) {
43
+ const base = 'px-4 py-2 font-medium text-sm whitespace-nowrap bg-transparent border-b-2 transition-colors cursor-pointer '
44
+ return activeTab() === id
45
+ ? base + 'border-violet-400 text-violet-600 dark:text-violet-400'
46
+ : base + 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
47
+ }
48
+
49
+ function selectTab(id) {
50
+ activeTab.set(id)
51
+ selectedUserId.set(null)
52
+ selectedRoleName.set(null)
53
+ }
54
+
55
+ // ─── Filters ──────────────────────────────────────────────────
56
+
57
+ const roleFilter = state('')
58
+ const permissionFilter = state('')
59
+ const userFilter = state('')
60
+
61
+ const filteredRoles = derived(() => {
62
+ const q = roleFilter().toLowerCase()
63
+ if (!q) return roles()
64
+ return roles().filter(r => r.name.toLowerCase().includes(q) || (r.description || '').toLowerCase().includes(q))
65
+ })
66
+
67
+ const filteredPermissions = derived(() => {
68
+ const q = permissionFilter().toLowerCase()
69
+ if (!q) return permissions()
70
+ return permissions().filter(p => p.name.toLowerCase().includes(q) || (p.description || '').toLowerCase().includes(q))
71
+ })
72
+
73
+ const filteredUsers = derived(() => {
74
+ const q = userFilter().toLowerCase()
75
+ if (!q) return users()
76
+ return users().filter(u => (u.name || '').toLowerCase().includes(q) || (u.email || '').toLowerCase().includes(q))
77
+ })
78
+
79
+ // ─── Create-role form ─────────────────────────────────────────
80
+
81
+ const newRoleName = state('')
82
+ const newRoleDescription = state('')
83
+ const newRoleGuard = state('web')
84
+ const submittingRole = state(false)
85
+
86
+ async function submitNewRole() {
87
+ const name = newRoleName().trim()
88
+ if (!name) return
89
+ submittingRole.set(true)
90
+ try {
91
+ const created = await rbac.createRole({
92
+ name,
93
+ guardName: newRoleGuard().trim() || 'web',
94
+ description: newRoleDescription().trim(),
95
+ })
96
+ if (created) {
97
+ newRoleName.set('')
98
+ newRoleDescription.set('')
99
+ }
100
+ }
101
+ finally {
102
+ submittingRole.set(false)
103
+ }
104
+ }
105
+
106
+ async function removeRole(name, guardName) {
107
+ // eslint-disable-next-line no-alert
108
+ if (typeof window !== 'undefined' && !window.confirm('Delete role "' + name + '"? This also detaches it from every user.'))
109
+ return
110
+ await rbac.deleteRole(name, guardName)
111
+ }
112
+
113
+ // ─── Create-permission form ───────────────────────────────────
114
+
115
+ const newPermName = state('')
116
+ const newPermDescription = state('')
117
+ const newPermGuard = state('web')
118
+ const submittingPerm = state(false)
119
+
120
+ async function submitNewPermission() {
121
+ const name = newPermName().trim()
122
+ if (!name) return
123
+ submittingPerm.set(true)
124
+ try {
125
+ const created = await rbac.createPermission({
126
+ name,
127
+ guardName: newPermGuard().trim() || 'web',
128
+ description: newPermDescription().trim(),
129
+ })
130
+ if (created) {
131
+ newPermName.set('')
132
+ newPermDescription.set('')
133
+ }
134
+ }
135
+ finally {
136
+ submittingPerm.set(false)
137
+ }
138
+ }
139
+
140
+ async function removePermission(name, guardName) {
141
+ // eslint-disable-next-line no-alert
142
+ if (typeof window !== 'undefined' && !window.confirm('Delete permission "' + name + '"? This also detaches it from every role.'))
143
+ return
144
+ await rbac.deletePermission(name, guardName)
145
+ }
146
+
147
+ // ─── User-role assignment drawer ──────────────────────────────
148
+
149
+ const selectedUserId = state(null)
150
+ const userRolesDraft = state([])
151
+ const savingUserRoles = state(false)
152
+
153
+ function selectUser(userId) {
154
+ selectedUserId.set(userId)
155
+ userRolesDraft.set([])
156
+ rbac.loadUserRoles(userId).then(() => {
157
+ userRolesDraft.set([...(rbac.userRoles()[userId] || [])])
158
+ })
159
+ }
160
+
161
+ function toggleUserRoleDraft(roleName) {
162
+ const list = userRolesDraft()
163
+ userRolesDraft.set(
164
+ list.includes(roleName)
165
+ ? list.filter(n => n !== roleName)
166
+ : [...list, roleName],
167
+ )
168
+ }
169
+
170
+ async function saveUserRoles() {
171
+ const uid = selectedUserId()
172
+ if (uid == null) return
173
+ savingUserRoles.set(true)
174
+ try {
175
+ await rbac.syncUserRoles(uid, userRolesDraft())
176
+ }
177
+ finally {
178
+ savingUserRoles.set(false)
179
+ }
180
+ }
181
+
182
+ function closeUserDrawer() {
183
+ selectedUserId.set(null)
184
+ userRolesDraft.set([])
185
+ }
186
+
187
+ function isRoleInDraft(roleName) {
188
+ return userRolesDraft().includes(roleName)
189
+ }
190
+
191
+ const selectedUser = derived(() => {
192
+ const uid = selectedUserId()
193
+ if (uid == null) return null
194
+ return users().find(u => u.id === uid) || null
195
+ })
196
+
197
+ // ─── Role-permission matrix ───────────────────────────────────
198
+
199
+ const selectedRoleName = state(null)
200
+ const rolePermsDraft = state([])
201
+ const savingRolePerms = state(false)
202
+
203
+ function selectRole(roleName, guardName) {
204
+ selectedRoleName.set(roleName)
205
+ rolePermsDraft.set([])
206
+ rbac.loadRolePermissions(roleName, guardName).then(() => {
207
+ rolePermsDraft.set([...(rbac.rolePermissions()[roleName] || [])])
208
+ })
209
+ }
210
+
211
+ function toggleRolePermDraft(permName) {
212
+ const list = rolePermsDraft()
213
+ rolePermsDraft.set(
214
+ list.includes(permName)
215
+ ? list.filter(n => n !== permName)
216
+ : [...list, permName],
217
+ )
218
+ }
219
+
220
+ async function saveRolePerms() {
221
+ const name = selectedRoleName()
222
+ if (!name) return
223
+ savingRolePerms.set(true)
224
+ try {
225
+ await rbac.syncRolePermissions(name, rolePermsDraft())
226
+ }
227
+ finally {
228
+ savingRolePerms.set(false)
229
+ }
230
+ }
231
+
232
+ function closeRoleDrawer() {
233
+ selectedRoleName.set(null)
234
+ rolePermsDraft.set([])
235
+ }
236
+
237
+ function isPermInDraft(permName) {
238
+ return rolePermsDraft().includes(permName)
239
+ }
240
+
241
+ const selectedRole = derived(() => {
242
+ const name = selectedRoleName()
243
+ if (!name) return null
244
+ return roles().find(r => r.name === name) || null
245
+ })
246
+
247
+ // ─── Tiny formatters ──────────────────────────────────────────
248
+
249
+ function formatDate(iso) {
250
+ if (!iso) return ''
251
+ const d = new Date(iso)
252
+ if (Number.isNaN(d.getTime())) return ''
253
+ return d.toLocaleDateString()
254
+ }
255
+
256
+ // Top-level fetch — runs on every page entry (hard reload + SPA swap).
257
+ // The store dedupes its own loading flag so back-to-back calls don't
258
+ // double-fire.
259
+ rbac.load()
260
+ </script>
261
+
262
+ @title('Dashboard - Permissions')
263
+ <div class="mx-auto px-6 py-8 max-w-7xl">
264
+ <!-- Role-gated wrapper: only admins can manage RBAC. Unauthenticated
265
+ viewers on localhost see the surface as if they were admin so the
266
+ dev dashboard stays usable; production must put the auth
267
+ middleware in front of `/api/dashboard/rbac/*` to make that
268
+ guarantee real. -->
269
+ <div :if="!canManage" class="px-5 py-8 text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
270
+ <p class="text-sm text-zinc-600 dark:text-zinc-400">This surface requires the admin role.</p>
271
+ </div>
272
+
273
+ <div :if="canManage">
274
+ <header class="mb-6">
275
+ <h1 class="font-semibold text-2xl text-zinc-900 dark:text-zinc-100">Permissions Management</h1>
276
+ <p class="mt-1 text-sm text-zinc-500">Manage roles, permissions, and user assignments. Backed by the live <code class="text-xs">user_roles</code> / <code class="text-xs">role_permissions</code> pivots.</p>
277
+ </header>
278
+
279
+ <!-- Error banner (initial load + transient mutation failures) -->
280
+ <div :if="error" class="mb-4 px-4 py-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950 border border-red-300 rounded-md dark:border-red-800">
281
+ <span :text="error"></span>
282
+ </div>
283
+
284
+ <!-- Loading state -->
285
+ <div :if="loading" class="px-5 py-8 text-center bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
286
+ <p class="text-sm text-zinc-500">Loading…</p>
287
+ </div>
288
+
289
+ <div :if="!loading">
290
+ <!-- Tabs -->
291
+ <div class="flex mb-6 border-b border-zinc-200 dark:border-zinc-800">
292
+ <button type="button" @click="selectTab('roles')" :class="tabClass('roles')">Roles</button>
293
+ <button type="button" @click="selectTab('permissions')" :class="tabClass('permissions')">Permissions</button>
294
+ <button type="button" @click="selectTab('users')" :class="tabClass('users')">Users</button>
295
+ <button type="button" @click="selectTab('matrix')" :class="tabClass('matrix')">Role × Permission</button>
296
+ </div>
297
+
298
+ <!-- ─── Roles tab ─────────────────────────────────────── -->
299
+ <div :if="activeTab() === 'roles'" class="space-y-6">
300
+ <div class="p-5 bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
301
+ <h2 class="mb-3 font-medium text-sm text-zinc-700 dark:text-zinc-300">Create role</h2>
302
+ <form @submit.prevent="submitNewRole()" class="grid gap-3 grid-cols-1 sm:grid-cols-4">
303
+ <input type="text" :value="newRoleName()" @input="newRoleName.set($event.target.value)" required maxlength="60" placeholder="Role name (e.g. editor)" class="block sm:col-span-2 px-3 py-1.5 text-sm text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
304
+ <input type="text" :value="newRoleDescription()" @input="newRoleDescription.set($event.target.value)" placeholder="Description (optional)" class="block px-3 py-1.5 text-sm text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
305
+ <button type="submit" :disabled="submittingRole()" class="block px-3 py-1.5 font-medium text-sm text-white bg-violet-600 hover:bg-violet-500 rounded-md disabled:opacity-50">Add role</button>
306
+ </form>
307
+ </div>
308
+
309
+ <div class="bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
310
+ <div class="flex gap-3 items-center justify-between px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
311
+ <h2 class="font-medium text-sm text-zinc-700 dark:text-zinc-300">Roles (<span :text="roles().length"></span>)</h2>
312
+ <input type="text" :value="roleFilter()" @input="roleFilter.set($event.target.value)" placeholder="Filter roles…" class="block px-3 py-1 w-64 text-xs text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
313
+ </div>
314
+ <div :if="filteredRoles().length === 0" class="px-5 py-8 text-center text-sm text-zinc-500">No roles yet.</div>
315
+ <table :if="filteredRoles().length > 0" class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-800">
316
+ <thead class="bg-zinc-50 dark:bg-neutral-950/50">
317
+ <tr>
318
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Name</th>
319
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Guard</th>
320
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Description</th>
321
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Created</th>
322
+ <th class="px-5 py-2 font-medium text-right text-xs text-zinc-500 tracking-wider uppercase">Actions</th>
323
+ </tr>
324
+ </thead>
325
+ <tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">
326
+ <template :for="role in filteredRoles">
327
+ <tr>
328
+ <td class="px-5 py-2 font-medium text-sm text-zinc-900 dark:text-zinc-100">{{ role.name }}</td>
329
+ <td class="px-5 py-2 text-xs text-zinc-500"><code>{{ role.guardName }}</code></td>
330
+ <td class="px-5 py-2 text-sm text-zinc-600 dark:text-zinc-400">{{ role.description || '—' }}</td>
331
+ <td class="px-5 py-2 text-xs text-zinc-500">{{ formatDate(role.createdAt) }}</td>
332
+ <td class="px-5 py-2 text-right text-sm">
333
+ <button type="button" @click="selectTab('matrix'); selectRole('{{ role.name }}', '{{ role.guardName }}')" class="mr-3 text-violet-600 dark:text-violet-400 hover:underline">Permissions</button>
334
+ <button type="button" @click="removeRole('{{ role.name }}', '{{ role.guardName }}')" class="text-red-600 dark:text-red-400 hover:underline">Delete</button>
335
+ </td>
336
+ </tr>
337
+ </template>
338
+ </tbody>
339
+ </table>
340
+ </div>
341
+ </div>
342
+
343
+ <!-- ─── Permissions tab ───────────────────────────────── -->
344
+ <div :if="activeTab() === 'permissions'" class="space-y-6">
345
+ <div class="p-5 bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
346
+ <h2 class="mb-3 font-medium text-sm text-zinc-700 dark:text-zinc-300">Create permission</h2>
347
+ <form @submit.prevent="submitNewPermission()" class="grid gap-3 grid-cols-1 sm:grid-cols-4">
348
+ <input type="text" :value="newPermName()" @input="newPermName.set($event.target.value)" required maxlength="100" placeholder="Permission name (e.g. posts.publish)" class="block sm:col-span-2 px-3 py-1.5 text-sm text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
349
+ <input type="text" :value="newPermDescription()" @input="newPermDescription.set($event.target.value)" placeholder="Description (optional)" class="block px-3 py-1.5 text-sm text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
350
+ <button type="submit" :disabled="submittingPerm()" class="block px-3 py-1.5 font-medium text-sm text-white bg-violet-600 hover:bg-violet-500 rounded-md disabled:opacity-50">Add permission</button>
351
+ </form>
352
+ </div>
353
+
354
+ <div class="bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
355
+ <div class="flex gap-3 items-center justify-between px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
356
+ <h2 class="font-medium text-sm text-zinc-700 dark:text-zinc-300">Permissions (<span :text="permissions().length"></span>)</h2>
357
+ <input type="text" :value="permissionFilter()" @input="permissionFilter.set($event.target.value)" placeholder="Filter permissions…" class="block px-3 py-1 w-64 text-xs text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
358
+ </div>
359
+ <div :if="filteredPermissions().length === 0" class="px-5 py-8 text-center text-sm text-zinc-500">No permissions yet.</div>
360
+ <table :if="filteredPermissions().length > 0" class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-800">
361
+ <thead class="bg-zinc-50 dark:bg-neutral-950/50">
362
+ <tr>
363
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Name</th>
364
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Guard</th>
365
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Description</th>
366
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Created</th>
367
+ <th class="px-5 py-2 font-medium text-right text-xs text-zinc-500 tracking-wider uppercase">Actions</th>
368
+ </tr>
369
+ </thead>
370
+ <tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">
371
+ <template :for="perm in filteredPermissions">
372
+ <tr>
373
+ <td class="px-5 py-2 font-medium text-sm text-zinc-900 dark:text-zinc-100">{{ perm.name }}</td>
374
+ <td class="px-5 py-2 text-xs text-zinc-500"><code>{{ perm.guardName }}</code></td>
375
+ <td class="px-5 py-2 text-sm text-zinc-600 dark:text-zinc-400">{{ perm.description || '—' }}</td>
376
+ <td class="px-5 py-2 text-xs text-zinc-500">{{ formatDate(perm.createdAt) }}</td>
377
+ <td class="px-5 py-2 text-right text-sm">
378
+ <button type="button" @click="removePermission('{{ perm.name }}', '{{ perm.guardName }}')" class="text-red-600 dark:text-red-400 hover:underline">Delete</button>
379
+ </td>
380
+ </tr>
381
+ </template>
382
+ </tbody>
383
+ </table>
384
+ </div>
385
+ </div>
386
+
387
+ <!-- ─── Users tab ─────────────────────────────────────── -->
388
+ <div :if="activeTab() === 'users'" class="space-y-6">
389
+ <div class="bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
390
+ <div class="flex gap-3 items-center justify-between px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
391
+ <h2 class="font-medium text-sm text-zinc-700 dark:text-zinc-300">Users (<span :text="users().length"></span>)</h2>
392
+ <input type="text" :value="userFilter()" @input="userFilter.set($event.target.value)" placeholder="Filter users…" class="block px-3 py-1 w-64 text-xs text-zinc-900 dark:text-zinc-100 dark:bg-neutral-800 border-0 ring-1 ring-zinc-300 rounded-md dark:ring-zinc-700 focus:ring-2 focus:ring-violet-500" />
393
+ </div>
394
+ <div :if="filteredUsers().length === 0" class="px-5 py-8 text-center text-sm text-zinc-500">No users found.</div>
395
+ <table :if="filteredUsers().length > 0" class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-800">
396
+ <thead class="bg-zinc-50 dark:bg-neutral-950/50">
397
+ <tr>
398
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Name</th>
399
+ <th class="px-5 py-2 font-medium text-left text-xs text-zinc-500 tracking-wider uppercase">Email</th>
400
+ <th class="px-5 py-2 font-medium text-right text-xs text-zinc-500 tracking-wider uppercase">Actions</th>
401
+ </tr>
402
+ </thead>
403
+ <tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">
404
+ <template :for="u in filteredUsers">
405
+ <tr>
406
+ <td class="px-5 py-2 font-medium text-sm text-zinc-900 dark:text-zinc-100">{{ u.name || '—' }}</td>
407
+ <td class="px-5 py-2 text-sm text-zinc-500">{{ u.email || '—' }}</td>
408
+ <td class="px-5 py-2 text-right text-sm">
409
+ <button type="button" @click="selectUser({{ u.id }})" class="text-violet-600 dark:text-violet-400 hover:underline">Manage roles</button>
410
+ </td>
411
+ </tr>
412
+ </template>
413
+ </tbody>
414
+ </table>
415
+ </div>
416
+ </div>
417
+
418
+ <!-- ─── Role × Permission matrix tab ──────────────────── -->
419
+ <div :if="activeTab() === 'matrix'" class="space-y-4">
420
+ <div class="p-5 bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
421
+ <p class="text-sm text-zinc-500">Pick a role to edit its permission attachments.</p>
422
+ </div>
423
+ <div :if="roles().length === 0" class="px-5 py-8 text-center text-sm text-zinc-500 bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800">No roles yet — create one on the Roles tab.</div>
424
+ <div :if="roles().length > 0" class="grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
425
+ <template :for="role in roles">
426
+ <button type="button" @click="selectRole('{{ role.name }}', '{{ role.guardName }}')" class="block px-4 py-3 text-left bg-white dark:bg-neutral-900 border border-zinc-200 rounded-lg dark:border-zinc-800 hover:border-violet-300 dark:hover:border-violet-700 transition-colors">
427
+ <div class="font-medium text-sm text-zinc-900 dark:text-zinc-100">{{ role.name }}</div>
428
+ <div class="mt-0.5 text-xs text-zinc-500">{{ role.description || 'No description' }}</div>
429
+ </button>
430
+ </template>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+
436
+ <!-- ─── User-role drawer (right-side panel) ───────────────── -->
437
+ <div :if="selectedUserId() !== null" class="fixed inset-0 z-30">
438
+ <div class="fixed inset-0 bg-black/50" @click="closeUserDrawer()"></div>
439
+ <div class="flex fixed inset-y-0 right-0 flex-col w-full sm:w-[28rem] bg-white dark:bg-neutral-900 shadow-xl">
440
+ <div class="flex items-center justify-between px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
441
+ <h3 class="font-medium text-sm text-zinc-900 dark:text-zinc-100">
442
+ Roles for <span :if="selectedUser()" :text="selectedUser().name || selectedUser().email || ('User #' + selectedUser().id)"></span>
443
+ </h3>
444
+ <button type="button" @click="closeUserDrawer()" class="text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300">Close</button>
445
+ </div>
446
+ <div class="overflow-y-auto flex-1 px-5 py-4 space-y-2">
447
+ <div :if="roles().length === 0" class="text-sm text-zinc-500">No roles defined yet.</div>
448
+ <template :for="role in roles">
449
+ <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">
450
+ <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" />
451
+ <span class="text-sm text-zinc-900 dark:text-zinc-100">{{ role.name }}</span>
452
+ <span class="text-xs text-zinc-500">{{ role.description || '' }}</span>
453
+ </label>
454
+ </template>
455
+ </div>
456
+ <div class="flex gap-2 items-center justify-end px-5 py-3 border-t border-zinc-200 dark:border-zinc-800">
457
+ <button type="button" @click="closeUserDrawer()" class="px-3 py-1.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-neutral-800 rounded-md">Cancel</button>
458
+ <button type="button" @click="saveUserRoles()" :disabled="savingUserRoles()" class="px-3 py-1.5 text-sm text-white bg-violet-600 hover:bg-violet-500 rounded-md disabled:opacity-50">Save</button>
459
+ </div>
460
+ </div>
461
+ </div>
462
+
463
+ <!-- ─── Role-permission drawer ────────────────────────────── -->
464
+ <div :if="selectedRoleName() !== null" class="fixed inset-0 z-30">
465
+ <div class="fixed inset-0 bg-black/50" @click="closeRoleDrawer()"></div>
466
+ <div class="flex fixed inset-y-0 right-0 flex-col w-full sm:w-[28rem] bg-white dark:bg-neutral-900 shadow-xl">
467
+ <div class="flex items-center justify-between px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
468
+ <h3 class="font-medium text-sm text-zinc-900 dark:text-zinc-100">
469
+ Permissions for <span :if="selectedRole()" :text="selectedRole().name"></span>
470
+ </h3>
471
+ <button type="button" @click="closeRoleDrawer()" class="text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300">Close</button>
472
+ </div>
473
+ <div class="overflow-y-auto flex-1 px-5 py-4 space-y-2">
474
+ <div :if="permissions().length === 0" class="text-sm text-zinc-500">No permissions defined yet.</div>
475
+ <template :for="perm in permissions">
476
+ <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">
477
+ <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" />
478
+ <span class="text-sm text-zinc-900 dark:text-zinc-100">{{ perm.name }}</span>
479
+ <span class="text-xs text-zinc-500">{{ perm.description || '' }}</span>
480
+ </label>
481
+ </template>
482
+ </div>
483
+ <div class="flex gap-2 items-center justify-end px-5 py-3 border-t border-zinc-200 dark:border-zinc-800">
484
+ <button type="button" @click="closeRoleDrawer()" class="px-3 py-1.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-neutral-800 rounded-md">Cancel</button>
485
+ <button type="button" @click="saveRolePerms()" :disabled="savingRolePerms()" class="px-3 py-1.5 text-sm text-white bg-violet-600 hover:bg-violet-500 rounded-md disabled:opacity-50">Save</button>
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ @endsection