@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,256 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <script client>
5
+ interface ConfigFile {
6
+ name: string
7
+ title: string
8
+ size: number
9
+ }
10
+
11
+ interface ConfigField {
12
+ key: string
13
+ value: string | number | boolean | null | unknown[] | Record<string, unknown>
14
+ type: string
15
+ editable: boolean
16
+ reason?: string
17
+ }
18
+
19
+ const files = state<ConfigFile[]>([])
20
+ const fields = state<ConfigField[]>([])
21
+ const drafts = state<Record<string, string | number | boolean>>({})
22
+ const selected = state('')
23
+ const query = state('')
24
+ const loading = state(true)
25
+ const saving = state(false)
26
+ const message = state('')
27
+ const error = state('')
28
+
29
+ const filteredFiles = derived(() => {
30
+ const needle = query().trim().toLowerCase()
31
+ if (!needle)
32
+ return files()
33
+
34
+ return files().filter(file => file.name.includes(needle) || file.title.toLowerCase().includes(needle))
35
+ })
36
+
37
+ const selectedFile = derived(() => files().find(file => file.name === selected()))
38
+ const editableCount = derived(() => fields().filter(field => field.editable).length)
39
+
40
+ function displayValue(field: ConfigField): string | number {
41
+ if (field.editable)
42
+ return drafts()[field.key] as string | number
43
+ if (field.type === 'object' || field.type === 'array')
44
+ return JSON.stringify(field.value)
45
+ return String(field.value ?? '')
46
+ }
47
+
48
+ function setDraft(key: string, value: string | number | boolean): void {
49
+ drafts.set({ ...drafts(), [key]: value })
50
+ }
51
+
52
+ async function loadFiles(): Promise<void> {
53
+ loading.set(true)
54
+ error.set('')
55
+ try {
56
+ const response = await fetch('/api/config/list')
57
+ const body = await response.json()
58
+ if (!response.ok || !body.ok)
59
+ throw new Error(body.error || 'Unable to list configuration files')
60
+
61
+ files.set(body.files)
62
+ const first = body.files[0]?.name
63
+ if (first)
64
+ await selectFile(first)
65
+ }
66
+ catch (cause) {
67
+ error.set(cause instanceof Error ? cause.message : 'Unable to load configuration')
68
+ }
69
+ finally {
70
+ loading.set(false)
71
+ }
72
+ }
73
+
74
+ async function selectFile(name: string): Promise<void> {
75
+ selected.set(name)
76
+ fields.set([])
77
+ message.set('')
78
+ error.set('')
79
+ loading.set(true)
80
+ try {
81
+ const response = await fetch(`/api/config/read?name=${encodeURIComponent(name)}`)
82
+ const body = await response.json()
83
+ if (!response.ok || !body.ok)
84
+ throw new Error(body.error || `Unable to read config/${name}.ts`)
85
+
86
+ fields.set(body.fields)
87
+ drafts.set(Object.fromEntries(
88
+ body.fields
89
+ .filter((field: ConfigField) => field.editable)
90
+ .map((field: ConfigField) => [field.key, field.value]),
91
+ ))
92
+ }
93
+ catch (cause) {
94
+ error.set(cause instanceof Error ? cause.message : `Unable to read config/${name}.ts`)
95
+ }
96
+ finally {
97
+ loading.set(false)
98
+ }
99
+ }
100
+
101
+ async function save(): Promise<void> {
102
+ const file = selected()
103
+ if (!file || saving())
104
+ return
105
+
106
+ saving.set(true)
107
+ message.set('')
108
+ error.set('')
109
+ try {
110
+ const updates = fields()
111
+ .filter(field => field.editable)
112
+ .map(field => ({ key: field.key, value: drafts()[field.key] }))
113
+ const response = await fetch('/api/config/update', {
114
+ method: 'POST',
115
+ headers: { 'content-type': 'application/json' },
116
+ body: JSON.stringify({ file, updates }),
117
+ })
118
+ const body = await response.json()
119
+ if (!response.ok || !body.ok) {
120
+ const failed = body.results?.filter((result: { ok: boolean }) => !result.ok)
121
+ throw new Error(failed?.map((result: { error?: string }) => result.error).filter(Boolean).join(', ') || body.error || 'Unable to save configuration')
122
+ }
123
+
124
+ message.set(`Saved config/${file}.ts`)
125
+ await selectFile(file)
126
+ message.set(`Saved config/${file}.ts`)
127
+ }
128
+ catch (cause) {
129
+ error.set(cause instanceof Error ? cause.message : 'Unable to save configuration')
130
+ }
131
+ finally {
132
+ saving.set(false)
133
+ }
134
+ }
135
+
136
+ void loadFiles()
137
+ </script>
138
+
139
+ <div class="mx-auto pb-12 pt-6 px-6 space-y-6 max-w-7xl">
140
+ <PageHeader
141
+ title="Settings"
142
+ description="Review and edit project configuration without losing comments or environment bindings"
143
+ />
144
+
145
+ <div class="grid gap-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
146
+ <Card variant="default" padding="none">
147
+ <div class="p-4 border-b border-neutral-200/60 dark:border-white/5">
148
+ <label for="config-search" class="sr-only">Search configuration</label>
149
+ <div class="relative">
150
+ <span class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
151
+ <input
152
+ id="config-search"
153
+ type="search"
154
+ placeholder="Search config"
155
+ :value="query()"
156
+ @input="query.set($event.target.value)"
157
+ class="block pl-9 pr-3 py-2 w-full text-neutral-900 text-sm dark:text-white placeholder:text-neutral-400 bg-white dark:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-500/50 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
158
+ >
159
+ </div>
160
+ </div>
161
+
162
+ <nav class="overflow-y-auto p-2 max-h-[68vh]" aria-label="Configuration files">
163
+ <template :for="file in filteredFiles">
164
+ <button
165
+ type="button"
166
+ :class="selected() === file.name ? 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-500/10 rounded-lg' : 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-white/5 rounded-lg'"
167
+ @click="selectFile(file.name)"
168
+ >
169
+ <span class="flex-none h-4 w-4 i-hugeicons-settings-02"></span>
170
+ <span class="flex-1 font-medium text-sm truncate">{{ file.title }}</span>
171
+ <span class="text-[10px] text-neutral-400">{{ file.size < 1024 ? file.size + ' B' : Math.round(file.size / 1024) + ' KB' }}</span>
172
+ </button>
173
+ </template>
174
+ </nav>
175
+ </Card>
176
+
177
+ <Card variant="default" padding="none">
178
+ @if(loading())
179
+ <div class="flex items-center justify-center min-h-80 text-neutral-500 dark:text-neutral-400">
180
+ <span class="mr-2 h-5 w-5 animate-spin i-hugeicons-loading-03"></span>
181
+ Loading configuration
182
+ </div>
183
+ @elseif(error() && !selectedFile())
184
+ <div class="p-8 text-center">
185
+ <span class="inline-block h-8 w-8 text-red-500 i-hugeicons-alert-02"></span>
186
+ <p class="mt-3 font-medium text-neutral-900 dark:text-white">Configuration unavailable</p>
187
+ <p class="mt-1 text-red-600 text-sm dark:text-red-400">{{ error() }}</p>
188
+ </div>
189
+ @elseif(selectedFile())
190
+ <div class="flex flex-wrap gap-4 items-center justify-between px-6 py-5 border-b border-neutral-200/60 dark:border-white/5">
191
+ <div>
192
+ <h2 class="font-semibold text-lg text-neutral-900 dark:text-white">{{ selectedFile().title }}</h2>
193
+ <p class="mt-1 font-mono text-neutral-500 text-xs dark:text-neutral-400">config/{{ selectedFile().name }}.ts</p>
194
+ </div>
195
+ <div class="flex gap-2 items-center">
196
+ <Badge variant="default" size="sm">{{ editableCount() }} editable</Badge>
197
+ <Button variant="primary" size="sm" :disabled="saving() || editableCount() === 0" @click="save()">
198
+ {{ saving() ? 'Saving...' : 'Save changes' }}
199
+ </Button>
200
+ </div>
201
+ </div>
202
+
203
+ @if(message())
204
+ <div class="mt-5 mx-6 px-4 py-3 text-green-700 text-sm dark:text-green-300 bg-green-50 dark:bg-green-500/10 border border-green-200 rounded-lg dark:border-green-500/20">
205
+ {{ message() }}
206
+ </div>
207
+ @endif
208
+ @if(error())
209
+ <div class="mt-5 mx-6 px-4 py-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-500/10 border border-red-200 rounded-lg dark:border-red-500/20">
210
+ {{ error() }}
211
+ </div>
212
+ @endif
213
+
214
+ <form class="divide-neutral-200/60 divide-y dark:divide-white/5" @submit.prevent="save()">
215
+ <template :for="field in fields">
216
+ <div class="grid gap-3 sm:grid-cols-[minmax(10rem,0.8fr)_minmax(0,1.2fr)] sm:items-start px-6 py-5">
217
+ <div>
218
+ <label :for="'config-' + field.key" class="font-medium text-neutral-900 text-sm dark:text-white">{{ field.key }}</label>
219
+ <p class="mt-1 text-neutral-500 text-xs dark:text-neutral-400">{{ field.type }}</p>
220
+ </div>
221
+
222
+ <div>
223
+ @if(field.type === 'boolean' && field.editable)
224
+ <label class="inline-flex gap-3 items-center cursor-pointer">
225
+ <input
226
+ :id="'config-' + field.key"
227
+ type="checkbox"
228
+ :checked="drafts()[field.key] === true"
229
+ @change="setDraft(field.key, $event.target.checked)"
230
+ class="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
231
+ >
232
+ <span class="text-neutral-700 text-sm dark:text-neutral-300">{{ drafts()[field.key] ? 'Enabled' : 'Disabled' }}</span>
233
+ </label>
234
+ @elseif(field.editable)
235
+ <input
236
+ :id="'config-' + field.key"
237
+ :type="field.type === 'number' ? 'number' : 'text'"
238
+ :value="displayValue(field)"
239
+ @input="setDraft(field.key, field.type === 'number' ? Number($event.target.value) : $event.target.value)"
240
+ class="block px-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-500/50 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
241
+ >
242
+ @else
243
+ <div class="px-3 py-2 break-all font-mono text-neutral-500 text-xs dark:text-neutral-400 bg-neutral-50 dark:bg-white/[0.03] border border-neutral-200 rounded-lg dark:border-white/10">
244
+ {{ displayValue(field) }}
245
+ </div>
246
+ <p class="mt-1.5 text-amber-700 text-xs dark:text-amber-300">{{ field.reason }}</p>
247
+ @endif
248
+ </div>
249
+ </div>
250
+ </template>
251
+ </form>
252
+ @endif
253
+ </Card>
254
+ </div>
255
+ </div>
256
+ @endsection
@@ -0,0 +1,252 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <script client>
5
+ // State-signal + directive style (matches the rest of the dashboard). The
6
+ // previous version used a Vue-style ref() composable, which stx's template
7
+ // runtime does not unwrap, so every binding on the page rendered empty; it
8
+ // also imported functions/api, whose auth chain runs useStorage() at module
9
+ // load and throws in the client bundle, killing the whole scope. So this
10
+ // talks to /api/settings/mail with a plain fetch, like the blog page.
11
+ const endpoint = '/api/settings/mail'
12
+
13
+ // CSRF header for writes, read from the cookie the GET on load seeds.
14
+ function writeHeaders() {
15
+ const headers = { 'Content-Type': 'application/json' }
16
+ const match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
17
+ if (match)
18
+ headers['X-CSRF-Token'] = decodeURIComponent(match[1])
19
+ return headers
20
+ }
21
+
22
+ const from = state('')
23
+ const to = state('')
24
+ const driver = state('SMTP')
25
+ const host = state('')
26
+ const port = state(587)
27
+ const username = state('')
28
+ const password = state('')
29
+ const isSaving = state(false)
30
+ const error = state('')
31
+ const success = state('')
32
+
33
+ async function load() {
34
+ try {
35
+ const res = await fetch(endpoint, { headers: { Accept: 'application/json' } })
36
+ if (!res.ok)
37
+ return
38
+ const json = await res.json()
39
+ const d = json && json.data ? json.data : json
40
+ if (d) {
41
+ from.set(d.from ?? '')
42
+ to.set(d.to ?? '')
43
+ driver.set(d.driver ?? 'SMTP')
44
+ host.set(d.host ?? '')
45
+ port.set(d.port ?? 587)
46
+ username.set(d.username ?? '')
47
+ password.set(d.password ?? '')
48
+ }
49
+ }
50
+ catch (e) {
51
+ // Keep the defaults when the settings endpoint isn't available yet.
52
+ }
53
+ }
54
+
55
+ async function save() {
56
+ isSaving.set(true)
57
+ error.set('')
58
+ success.set('')
59
+ try {
60
+ const res = await fetch(endpoint, {
61
+ method: 'PUT',
62
+ headers: writeHeaders(),
63
+ body: JSON.stringify({
64
+ from: from(),
65
+ to: to(),
66
+ driver: driver(),
67
+ host: host(),
68
+ port: port(),
69
+ username: username(),
70
+ password: password(),
71
+ }),
72
+ })
73
+ if (!res.ok)
74
+ throw new Error(`Save failed (${res.status})`)
75
+ success.set('Mail settings saved successfully.')
76
+ }
77
+ catch (e) {
78
+ error.set(e instanceof Error ? e.message : 'Failed to save mail settings.')
79
+ }
80
+ finally {
81
+ isSaving.set(false)
82
+ }
83
+ }
84
+
85
+ onMount(() => {
86
+ load()
87
+ })
88
+ </script>
89
+
90
+ <!-- Plain container instead of a component wrapper: a component slot severs the
91
+ page reactive scope from the markup, leaving every field binding empty. The
92
+ sibling settings pages avoid the wrapper for the same reason. -->
93
+ <div class="mx-auto pb-12 pt-6 px-6 space-y-6 max-w-7xl">
94
+ <PageHeader title="Email Settings" description="Configure how your application sends mail." />
95
+
96
+ <!-- Settings forms -->
97
+ <div class="divide-white/5 divide-y">
98
+ <div class="grid gap-x-8 gap-y-10 grid-cols-1 md:grid-cols-3 px-4 py-16 sm:px-6 lg:px-8 max-w-7xl">
99
+ <div>
100
+ <h2 class="font-semibold leading-7 text-base text-gray-900 dark:text-neutral-100">
101
+ Main Config
102
+ </h2>
103
+ <p class="mt-1 leading-6 text-gray-600 text-sm">
104
+ Update mail settings.
105
+ </p>
106
+ </div>
107
+
108
+ <form class="md:col-span-2" @submit.prevent="save()">
109
+ <!-- Success message -->
110
+ <div x-show="success()" class="mb-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-md">
111
+ <p class="text-green-700 text-sm dark:text-green-400">{{ success() }}</p>
112
+ </div>
113
+
114
+ <!-- Error message -->
115
+ <div x-show="error()" class="mb-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-md">
116
+ <p class="text-red-700 text-sm dark:text-red-400">{{ error() }}</p>
117
+ </div>
118
+
119
+ <div class="grid gap-x-6 gap-y-8 grid-cols-1 sm:grid-cols-6 sm:max-w-xl">
120
+ <div class="sm:col-span-full">
121
+ <label
122
+ for="mail-from"
123
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
124
+ >Mail From</label>
125
+ <div class="mt-2">
126
+ <input
127
+ id="mail-from"
128
+ type="email"
129
+ x-model="from"
130
+ placeholder="no-reply@example.com"
131
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
132
+ >
133
+ </div>
134
+ </div>
135
+
136
+ <div class="sm:col-span-full">
137
+ <label
138
+ for="mail-to"
139
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
140
+ >Mail To</label>
141
+ <div class="mt-2">
142
+ <input
143
+ id="mail-to"
144
+ type="email"
145
+ x-model="to"
146
+ placeholder="admin@example.com"
147
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
148
+ >
149
+ </div>
150
+ </div>
151
+
152
+ <div class="sm:col-span-full">
153
+ <label
154
+ for="mail-driver"
155
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
156
+ >Driver</label>
157
+ <div class="mt-2">
158
+ <select
159
+ id="mail-driver"
160
+ x-model="driver"
161
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
162
+ >
163
+ <option value="SMTP">SMTP</option>
164
+ <option value="ses">Amazon SES</option>
165
+ <option value="sendgrid">SendGrid</option>
166
+ <option value="mailgun">Mailgun</option>
167
+ <option value="mailtrap">Mailtrap</option>
168
+ </select>
169
+ </div>
170
+ </div>
171
+
172
+ <div class="sm:col-span-3">
173
+ <label
174
+ for="mail-host"
175
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
176
+ >Host</label>
177
+ <div class="mt-2">
178
+ <input
179
+ id="mail-host"
180
+ type="text"
181
+ x-model="host"
182
+ placeholder="smtp.example.com"
183
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
184
+ >
185
+ </div>
186
+ </div>
187
+
188
+ <div class="sm:col-span-3">
189
+ <label
190
+ for="mail-port"
191
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
192
+ >Port</label>
193
+ <div class="mt-2">
194
+ <input
195
+ id="mail-port"
196
+ type="number"
197
+ x-model.number="port"
198
+ placeholder="587"
199
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
200
+ >
201
+ </div>
202
+ </div>
203
+
204
+ <div class="sm:col-span-3">
205
+ <label
206
+ for="mail-username"
207
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
208
+ >Username</label>
209
+ <div class="mt-2">
210
+ <input
211
+ id="mail-username"
212
+ type="text"
213
+ x-model="username"
214
+ placeholder="your-username"
215
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
216
+ >
217
+ </div>
218
+ </div>
219
+
220
+ <div class="sm:col-span-3">
221
+ <label
222
+ for="mail-password"
223
+ class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
224
+ >Password</label>
225
+ <div class="mt-2">
226
+ <input
227
+ id="mail-password"
228
+ type="password"
229
+ x-model="password"
230
+ autocomplete="off"
231
+ placeholder="••••••••"
232
+ class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
233
+ >
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <div class="flex mt-8">
239
+ <button
240
+ type="submit"
241
+ :disabled="isSaving()"
242
+ class="px-3 py-2 font-semibold 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 disabled:opacity-50 disabled:cursor-not-allowed"
243
+ >
244
+ {{ isSaving() ? 'Saving...' : 'Save Settings' }}
245
+ </button>
246
+ </div>
247
+ </form>
248
+ </div>
249
+ </div>
250
+ </div>
251
+
252
+ @endsection
@@ -0,0 +1,123 @@
1
+ import { defineStore, derived, registerStoresClient, state } from '@stacksjs/stx'
2
+
3
+ /**
4
+ * Dashboard auth/identity store — single source of truth for "who is
5
+ * looking at the dashboard right now and what can they see?".
6
+ *
7
+ * Backs the `useRole()` composable
8
+ * (stacksjs/stacks#1843, plumbing landed for #1844). Fetches from
9
+ * `/api/dashboard/auth/me` on first use; subsequent consumers hit the
10
+ * cached signal.
11
+ *
12
+ * Auth modes the store is designed to handle:
13
+ *
14
+ * 1. **Local dev dashboard, no auth** — `/api/dashboard/auth/me`
15
+ * returns `{ user: null, roles: [], unauthenticated: true }`.
16
+ * We treat that as "dev mode" so dev-only surfaces (CI tracking,
17
+ * runner alerts, query inspector) stay visible without forcing
18
+ * anyone to authenticate against their own dev DB.
19
+ * 2. **Authenticated** — the endpoint returns the real user + their
20
+ * RBAC role names from the `user_roles` pivot. The role-derived
21
+ * flags (`isAdmin`, `isDev`, `isClient`) reflect that membership.
22
+ * 3. **Fetch failed** — network down, route 404, server error.
23
+ * `error` is set, but the role flags still default to "permissive
24
+ * dev" so a broken endpoint doesn't lock the dashboard.
25
+ */
26
+ export const authStore = defineStore('auth', () => {
27
+ const userId = state<number | null>(null)
28
+ const userName = state<string | null>(null)
29
+ const userEmail = state<string | null>(null)
30
+ const roles = state<string[]>([])
31
+ const unauthenticated = state(true)
32
+ const loading = state(false)
33
+ const error = state<string | null>(null)
34
+ const loaded = state(false)
35
+
36
+ // Permissive-dev defaults: when nobody is signed in (or the endpoint
37
+ // failed), assume the viewer is a dev. The alternative — hiding every
38
+ // gated surface until auth is wired — would make the dashboard look
39
+ // broken on a fresh project.
40
+ const isAdmin = derived(() => {
41
+ if (unauthenticated()) return true
42
+ return roles().includes('admin')
43
+ })
44
+ const isDev = derived(() => {
45
+ if (unauthenticated()) return true
46
+ return roles().includes('admin') || roles().includes('dev')
47
+ })
48
+ const isClient = derived(() => {
49
+ if (unauthenticated()) return false
50
+ return roles().includes('client')
51
+ })
52
+
53
+ async function load(): Promise<void> {
54
+ if (loading() || loaded()) return
55
+ loading.set(true)
56
+ error.set(null)
57
+ try {
58
+ const res = await fetch('/api/dashboard/auth/me', { headers: { accept: 'application/json' } })
59
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
60
+ const data = await res.json() as {
61
+ user: { id: number, name: string | null, email: string | null } | null
62
+ roles: string[]
63
+ unauthenticated?: true
64
+ }
65
+ if (data.user) {
66
+ userId.set(data.user.id)
67
+ userName.set(data.user.name)
68
+ userEmail.set(data.user.email)
69
+ }
70
+ else {
71
+ userId.set(null)
72
+ userName.set(null)
73
+ userEmail.set(null)
74
+ }
75
+ roles.set(data.roles ?? [])
76
+ unauthenticated.set(Boolean(data.unauthenticated))
77
+ loaded.set(true)
78
+ }
79
+ catch (e) {
80
+ error.set(e instanceof Error ? e.message : String(e))
81
+ // Keep the permissive defaults: don't mark `loaded` so a later
82
+ // explicit `refresh()` can retry.
83
+ }
84
+ finally {
85
+ loading.set(false)
86
+ }
87
+ }
88
+
89
+ async function refresh(): Promise<void> {
90
+ loaded.set(false)
91
+ await load()
92
+ }
93
+
94
+ return {
95
+ userId,
96
+ userName,
97
+ userEmail,
98
+ roles,
99
+ unauthenticated,
100
+ loading,
101
+ error,
102
+ loaded,
103
+ isAdmin,
104
+ isDev,
105
+ isClient,
106
+ load,
107
+ refresh,
108
+ }
109
+ }, {
110
+ persist: {
111
+ storage: 'sessionStorage',
112
+ key: 'stacks-dashboard-auth',
113
+ // Don't persist `loaded` — every fresh tab should re-fetch identity
114
+ // (the user may have been assigned a new role, or signed out, since
115
+ // the last visit). Roles + user info ride along as a soft cache so
116
+ // the dashboard renders against last-known state before the
117
+ // round-trip resolves.
118
+ pick: ['userId', 'userName', 'userEmail', 'roles', 'unauthenticated'],
119
+ },
120
+ })
121
+
122
+ if (typeof window !== 'undefined')
123
+ registerStoresClient({ authStore })