@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,350 @@
1
+ import { defineStore, derived, registerStoresClient, state } from '@stacksjs/stx'
2
+
3
+ interface RoleRow {
4
+ id: number
5
+ name: string
6
+ guardName: string
7
+ description: string | null
8
+ createdAt: string | null
9
+ }
10
+
11
+ interface PermissionRow {
12
+ id: number
13
+ name: string
14
+ guardName: string
15
+ description: string | null
16
+ createdAt: string | null
17
+ }
18
+
19
+ interface UserRow {
20
+ id: number
21
+ name: string | null
22
+ email: string | null
23
+ }
24
+
25
+ /**
26
+ * RBAC management store — backs the `/management/permissions` page
27
+ * (stacksjs/stacks#1845). Loads roles, permissions, and users
28
+ * once on page mount and keeps them in sync via the
29
+ * `/api/dashboard/rbac/*` endpoints. All mutations follow the
30
+ * snapshot → optimistic update → API call → rollback-on-failure
31
+ * shape established by the kanban + ci stores earlier in the
32
+ * session.
33
+ *
34
+ * The two pivot reads (user → roles, role → permissions) are
35
+ * lazy-loaded per selection because they only matter when the
36
+ * admin actually opens the relevant tab/row.
37
+ */
38
+ export const rbacStore = defineStore('rbac', () => {
39
+ const roles = state<RoleRow[]>([])
40
+ const permissions = state<PermissionRow[]>([])
41
+ const users = state<UserRow[]>([])
42
+
43
+ // Per-user role memberships, keyed by user id. Lazy-loaded when
44
+ // the admin picks a user on the Users tab so the page doesn't
45
+ // burn N+1 round-trips just to show the user list.
46
+ const userRoles = state<Record<number, string[]>>({})
47
+ // Per-role permission memberships, keyed by role name (the URL
48
+ // identifier on the matrix endpoint).
49
+ const rolePermissions = state<Record<string, string[]>>({})
50
+
51
+ const loading = state(true)
52
+ const loadingPivots = state<Record<string, boolean>>({})
53
+ const error = state<string | null>(null)
54
+
55
+ const hasRoles = derived(() => roles().length > 0)
56
+ const hasPermissions = derived(() => permissions().length > 0)
57
+
58
+ // ─── Initial load ──────────────────────────────────────────────
59
+
60
+ async function load(): Promise<void> {
61
+ loading.set(true)
62
+ error.set(null)
63
+ try {
64
+ const [rolesRes, permsRes, usersRes] = await Promise.all([
65
+ fetch('/api/dashboard/rbac/roles', { headers: { accept: 'application/json' } }),
66
+ fetch('/api/dashboard/rbac/permissions', { headers: { accept: 'application/json' } }),
67
+ fetch('/api/dashboard/rbac/users', { headers: { accept: 'application/json' } }),
68
+ ])
69
+ const [rolesData, permsData, usersData] = await Promise.all([
70
+ rolesRes.json() as Promise<{ roles?: RoleRow[], error?: string }>,
71
+ permsRes.json() as Promise<{ permissions?: PermissionRow[], error?: string }>,
72
+ usersRes.json() as Promise<{ users?: UserRow[], error?: string }>,
73
+ ])
74
+ // Pick the first error if any of the three failed — the page
75
+ // renders a single banner rather than three.
76
+ const firstError = rolesData.error || permsData.error || usersData.error
77
+ if (firstError)
78
+ error.set(firstError)
79
+
80
+ roles.set(rolesData.roles ?? [])
81
+ permissions.set(permsData.permissions ?? [])
82
+ users.set(usersData.users ?? [])
83
+ }
84
+ catch (e) {
85
+ error.set(e instanceof Error ? e.message : String(e))
86
+ }
87
+ finally {
88
+ loading.set(false)
89
+ }
90
+ }
91
+
92
+ // ─── Role mutations ────────────────────────────────────────────
93
+
94
+ async function createRole(input: { name: string, guardName?: string, description?: string }): Promise<RoleRow | null> {
95
+ try {
96
+ const res = await fetch('/api/dashboard/rbac/roles', {
97
+ method: 'POST',
98
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
99
+ body: JSON.stringify(input),
100
+ })
101
+ if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`)
102
+ const data = await res.json() as { role?: RoleRow, error?: string }
103
+ if (data.error || !data.role) throw new Error(data.error ?? 'Create failed')
104
+ roles.set([...roles(), data.role])
105
+ return data.role
106
+ }
107
+ catch (e) {
108
+ error.set(e instanceof Error ? e.message : String(e))
109
+ return null
110
+ }
111
+ }
112
+
113
+ async function deleteRole(name: string, guardName: string = 'web'): Promise<boolean> {
114
+ const snapshot = roles()
115
+ roles.set(snapshot.filter(r => !(r.name === name && r.guardName === guardName)))
116
+ // Cascade: a deleted role drops out of every user's role list +
117
+ // its row in the rolePermissions cache. Mirror the server-side
118
+ // cascade so the optimistic UI doesn't show stale attachments.
119
+ const userRolesSnapshot = userRoles()
120
+ const cleanedUserRoles: Record<number, string[]> = {}
121
+ for (const [uid, names] of Object.entries(userRolesSnapshot))
122
+ cleanedUserRoles[Number(uid)] = names.filter(n => n !== name)
123
+ userRoles.set(cleanedUserRoles)
124
+
125
+ const rolePermsSnapshot = rolePermissions()
126
+ const cleanedRolePerms = { ...rolePermsSnapshot }
127
+ delete cleanedRolePerms[name]
128
+ rolePermissions.set(cleanedRolePerms)
129
+
130
+ try {
131
+ const res = await fetch(
132
+ `/api/dashboard/rbac/roles/${encodeURIComponent(name)}?guard=${encodeURIComponent(guardName)}`,
133
+ { method: 'DELETE', headers: { accept: 'application/json' } },
134
+ )
135
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
136
+ return true
137
+ }
138
+ catch (e) {
139
+ // Rollback every cascade we applied optimistically.
140
+ roles.set(snapshot)
141
+ userRoles.set(userRolesSnapshot)
142
+ rolePermissions.set(rolePermsSnapshot)
143
+ error.set(e instanceof Error ? e.message : String(e))
144
+ return false
145
+ }
146
+ }
147
+
148
+ // ─── Permission mutations ──────────────────────────────────────
149
+
150
+ async function createPermission(input: { name: string, guardName?: string, description?: string }): Promise<PermissionRow | null> {
151
+ try {
152
+ const res = await fetch('/api/dashboard/rbac/permissions', {
153
+ method: 'POST',
154
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
155
+ body: JSON.stringify(input),
156
+ })
157
+ if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`)
158
+ const data = await res.json() as { permission?: PermissionRow, error?: string }
159
+ if (data.error || !data.permission) throw new Error(data.error ?? 'Create failed')
160
+ permissions.set([...permissions(), data.permission])
161
+ return data.permission
162
+ }
163
+ catch (e) {
164
+ error.set(e instanceof Error ? e.message : String(e))
165
+ return null
166
+ }
167
+ }
168
+
169
+ async function deletePermission(name: string, guardName: string = 'web'): Promise<boolean> {
170
+ const snapshot = permissions()
171
+ permissions.set(snapshot.filter(p => !(p.name === name && p.guardName === guardName)))
172
+ // Cascade through the rolePermissions cache: each role drops the
173
+ // deleted permission from its attached list.
174
+ const rolePermsSnapshot = rolePermissions()
175
+ const cleaned: Record<string, string[]> = {}
176
+ for (const [roleName, names] of Object.entries(rolePermsSnapshot))
177
+ cleaned[roleName] = names.filter(n => n !== name)
178
+ rolePermissions.set(cleaned)
179
+
180
+ try {
181
+ const res = await fetch(
182
+ `/api/dashboard/rbac/permissions/${encodeURIComponent(name)}?guard=${encodeURIComponent(guardName)}`,
183
+ { method: 'DELETE', headers: { accept: 'application/json' } },
184
+ )
185
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
186
+ return true
187
+ }
188
+ catch (e) {
189
+ permissions.set(snapshot)
190
+ rolePermissions.set(rolePermsSnapshot)
191
+ error.set(e instanceof Error ? e.message : String(e))
192
+ return false
193
+ }
194
+ }
195
+
196
+ // ─── User-role pivot ───────────────────────────────────────────
197
+
198
+ async function loadUserRoles(userId: number): Promise<void> {
199
+ if (userRoles()[userId] !== undefined) return
200
+ const key = `user:${userId}`
201
+ if (loadingPivots()[key]) return
202
+ loadingPivots.set({ ...loadingPivots(), [key]: true })
203
+ try {
204
+ const res = await fetch(`/api/dashboard/rbac/users/${userId}/roles`, {
205
+ headers: { accept: 'application/json' },
206
+ })
207
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
208
+ const data = await res.json() as { roles?: Array<{ name: string }>, error?: string }
209
+ if (data.error) {
210
+ userRoles.set({ ...userRoles(), [userId]: [] })
211
+ return
212
+ }
213
+ userRoles.set({
214
+ ...userRoles(),
215
+ [userId]: (data.roles ?? []).map(r => r.name),
216
+ })
217
+ }
218
+ catch {
219
+ // Soft-fail — page shows an empty role list for the user.
220
+ userRoles.set({ ...userRoles(), [userId]: [] })
221
+ }
222
+ finally {
223
+ const next = { ...loadingPivots() }
224
+ delete next[key]
225
+ loadingPivots.set(next)
226
+ }
227
+ }
228
+
229
+ async function syncUserRoles(userId: number, roleNames: string[], guardName: string = 'web'): Promise<boolean> {
230
+ const snapshot = userRoles()[userId] ?? []
231
+ // Optimistic mutation.
232
+ userRoles.set({ ...userRoles(), [userId]: roleNames })
233
+ try {
234
+ const res = await fetch(`/api/dashboard/rbac/users/${userId}/roles`, {
235
+ method: 'POST',
236
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
237
+ body: JSON.stringify({ roles: roleNames, guardName }),
238
+ })
239
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
240
+ const data = await res.json() as { roles?: Array<{ name: string }>, error?: string }
241
+ if (data.error) throw new Error(data.error)
242
+ // Canonical reconciliation — server returns the final list.
243
+ userRoles.set({
244
+ ...userRoles(),
245
+ [userId]: (data.roles ?? []).map(r => r.name),
246
+ })
247
+ return true
248
+ }
249
+ catch (e) {
250
+ userRoles.set({ ...userRoles(), [userId]: snapshot })
251
+ error.set(e instanceof Error ? e.message : String(e))
252
+ return false
253
+ }
254
+ }
255
+
256
+ // ─── Role-permission pivot ─────────────────────────────────────
257
+
258
+ async function loadRolePermissions(roleName: string, guardName: string = 'web'): Promise<void> {
259
+ if (rolePermissions()[roleName] !== undefined) return
260
+ const key = `role:${roleName}`
261
+ if (loadingPivots()[key]) return
262
+ loadingPivots.set({ ...loadingPivots(), [key]: true })
263
+ try {
264
+ const res = await fetch(
265
+ `/api/dashboard/rbac/roles/${encodeURIComponent(roleName)}/permissions?guard=${encodeURIComponent(guardName)}`,
266
+ { headers: { accept: 'application/json' } },
267
+ )
268
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
269
+ const data = await res.json() as { permissions?: Array<{ name: string }>, error?: string }
270
+ if (data.error) {
271
+ rolePermissions.set({ ...rolePermissions(), [roleName]: [] })
272
+ return
273
+ }
274
+ rolePermissions.set({
275
+ ...rolePermissions(),
276
+ [roleName]: (data.permissions ?? []).map(p => p.name),
277
+ })
278
+ }
279
+ catch {
280
+ rolePermissions.set({ ...rolePermissions(), [roleName]: [] })
281
+ }
282
+ finally {
283
+ const next = { ...loadingPivots() }
284
+ delete next[key]
285
+ loadingPivots.set(next)
286
+ }
287
+ }
288
+
289
+ async function syncRolePermissions(roleName: string, permissionNames: string[], guardName: string = 'web'): Promise<boolean> {
290
+ const snapshot = rolePermissions()[roleName] ?? []
291
+ rolePermissions.set({ ...rolePermissions(), [roleName]: permissionNames })
292
+ try {
293
+ const res = await fetch(
294
+ `/api/dashboard/rbac/roles/${encodeURIComponent(roleName)}/permissions`,
295
+ {
296
+ method: 'POST',
297
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
298
+ body: JSON.stringify({ permissions: permissionNames, guardName }),
299
+ },
300
+ )
301
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
302
+ const data = await res.json() as { permissions?: Array<{ name: string }>, error?: string }
303
+ if (data.error) throw new Error(data.error)
304
+ rolePermissions.set({
305
+ ...rolePermissions(),
306
+ [roleName]: (data.permissions ?? []).map(p => p.name),
307
+ })
308
+ return true
309
+ }
310
+ catch (e) {
311
+ rolePermissions.set({ ...rolePermissions(), [roleName]: snapshot })
312
+ error.set(e instanceof Error ? e.message : String(e))
313
+ return false
314
+ }
315
+ }
316
+
317
+ return {
318
+ roles,
319
+ permissions,
320
+ users,
321
+ userRoles,
322
+ rolePermissions,
323
+ loading,
324
+ loadingPivots,
325
+ error,
326
+ hasRoles,
327
+ hasPermissions,
328
+ load,
329
+ createRole,
330
+ deleteRole,
331
+ createPermission,
332
+ deletePermission,
333
+ loadUserRoles,
334
+ syncUserRoles,
335
+ loadRolePermissions,
336
+ syncRolePermissions,
337
+ }
338
+ }, {
339
+ persist: {
340
+ storage: 'sessionStorage',
341
+ key: 'stacks-dashboard-rbac',
342
+ // Don't persist the pivot caches across page reloads — they're
343
+ // cheap to re-fetch and stale data on a sensitive admin surface
344
+ // is worse than a 100ms refetch.
345
+ pick: [],
346
+ },
347
+ })
348
+
349
+ if (typeof window !== 'undefined')
350
+ registerStoresClient({ rbacStore })
@@ -0,0 +1,34 @@
1
+ export default {
2
+ root: 'resources',
3
+ pagesDir: 'views',
4
+ // STX walks `componentsDir` recursively when resolving `<TagName />` against
5
+ // the filesystem, so dropping the `views/` prefix here points at the real
6
+ // shared components tree (`resources/components/Dashboard/UI/...`) and lets
7
+ // pages use `<PageLayout />`, `<PageHeader />`, etc. without an explicit
8
+ // `import` line in `<script server>`.
9
+ componentsDir: 'components',
10
+ layoutsDir: 'views/layouts',
11
+ partialsDir: 'views/partials',
12
+
13
+ app: {
14
+ head: {
15
+ title: 'Stacks',
16
+ meta: [
17
+ { charset: 'utf-8' },
18
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
19
+ { name: 'description', content: 'Build modern web applications, APIs, and cloud infrastructure with an elegant, type-safe framework.' },
20
+ ],
21
+ links: [
22
+ { rel: 'preconnect', href: 'https://fonts.bunny.net' },
23
+ { rel: 'stylesheet', href: 'https://fonts.bunny.net/css?family=figtree:400,500,600,700' },
24
+ ],
25
+ bodyClass: 'min-h-screen font-sans antialiased bg-white dark:bg-neutral-950 text-neutral-600 dark:text-neutral-400',
26
+ },
27
+ },
28
+
29
+ router: {
30
+ container: 'main',
31
+ },
32
+
33
+ strict: false,
34
+ }
@@ -0,0 +1,170 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <div class="p-6 space-y-6">
5
+
6
+ <PageHeader title="Team Members" description="Manage your team members and invitations" />
7
+
8
+ <form class="px-6 py-4 text-sm bg-white dark:bg-neutral-800 rounded-lg" @submit.prevent="handleInvite">
9
+ <div class="py-2">
10
+ <div class="flex flex-row p-4 text-gray-600 text-sm dark:text-neutral-400 bg-gray-50 dark:bg-neutral-700 rounded-lg">
11
+ <div class="flex-shrink-0 h-6 w-6 text-gray-400 i-hugeicons-information-circle"></div>
12
+ <div class="flex flex-grow ml-4 mt-0.5">
13
+ <p>You can add teammates here.</p>
14
+ </div>
15
+ </div>
16
+
17
+ <div :show="hasInviteSuccess" class="mt-3 p-3 bg-green-50 dark:bg-green-900/20 rounded-md">
18
+ <p class="text-green-700 text-sm dark:text-green-400">{{ inviteSuccess }}</p>
19
+ </div>
20
+
21
+ <div :show="hasInviteError" class="mt-3 p-3 bg-red-50 dark:bg-red-900/20 rounded-md">
22
+ <p class="text-red-700 text-sm dark:text-red-400">{{ inviteError }}</p>
23
+ </div>
24
+
25
+ <div class="flex flex-col mt-4">
26
+ <label class="text-gray-700 dark:text-neutral-300">Enter teammate email to invite them.</label>
27
+ <div class="flex flex-col mt-1">
28
+ <input
29
+ type="email"
30
+ x-model="inviteEmail"
31
+ placeholder="teammate@example.com"
32
+ 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"
33
+ autocorrect="off"
34
+ />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <div>
39
+ <div class="flex justify-end mt-4">
40
+ <Button type="submit" variant="primary" :disabled="isInviting">{{ inviteButtonLabel() }}</Button>
41
+ </div>
42
+ </div>
43
+ </form>
44
+
45
+ <div class="mt-16 px-6 py-4 text-sm bg-white dark:bg-neutral-800 rounded-lg">
46
+ <div class="sm:flex sm:items-center">
47
+ <div class="sm:flex-auto">
48
+ <h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Teammates</h1>
49
+ </div>
50
+ </div>
51
+ <div class="flow-root mt-8">
52
+ <div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
53
+ <div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
54
+ <table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
55
+ <thead>
56
+ <tr>
57
+ <th scope="col" class="pl-4 pr-3 py-3 sm:pl-0 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Name</th>
58
+ <th scope="col" class="px-3 py-3 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Email</th>
59
+ <th scope="col" class="px-3 py-3 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Role</th>
60
+ <th scope="col" class="relative pl-3 pr-4 py-3 sm:pr-0"><span class="sr-only">Actions</span></th>
61
+ </tr>
62
+ </thead>
63
+ <tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-600">
64
+ <tr :show="isLoading">
65
+ <td colspan="4" class="py-8 text-center text-gray-500 text-sm dark:text-neutral-400">Loading teammates...</td>
66
+ </tr>
67
+ <tr :show="isEmpty">
68
+ <td colspan="4" class="py-8 text-center text-gray-500 text-sm dark:text-neutral-400">No teammates yet. Invite someone above.</td>
69
+ </tr>
70
+ <template :for="teammate in teammates">
71
+ <tr>
72
+ <td class="pl-4 pr-3 py-4 sm:pl-0 font-medium text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ teammate.name }}</td>
73
+ <td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ teammate.email }}</td>
74
+ <td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ teammate.role }}</td>
75
+ <td class="relative pl-3 pr-4 py-4 sm:pr-0 font-medium text-right text-sm whitespace-nowrap">
76
+ <button class="text-gray-400 hover:text-neutral-500 duration-150 transition-colors">
77
+ <div class="h-6 w-6 i-hugeicons-ellipsis-vertical"></div>
78
+ </button>
79
+ </td>
80
+ </tr>
81
+ </template>
82
+ </tbody>
83
+ </table>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <script client>
91
+ import { get, post } from '../../../functions/api'
92
+
93
+ interface Teammate {
94
+ id?: number
95
+ name: string
96
+ email: string
97
+ role: string
98
+ }
99
+
100
+ const route = useRoute()
101
+
102
+ const teammates = state<Teammate[]>([])
103
+ const isLoading = state(true)
104
+
105
+ const inviteEmail = state('')
106
+ const inviteError = state('')
107
+ const inviteSuccess = state('')
108
+ const isInviting = state(false)
109
+
110
+ const hasInviteSuccess = derived(() => inviteSuccess() !== '')
111
+ const hasInviteError = derived(() => inviteError() !== '')
112
+ const isEmpty = derived(() => !isLoading() && teammates().length === 0)
113
+
114
+ function inviteButtonLabel(): string {
115
+ return isInviting() ? 'Inviting...' : 'Invite'
116
+ }
117
+
118
+ async function fetchTeammates(): Promise<void> {
119
+ isLoading.set(true)
120
+ try {
121
+ const data = await get(`/teams/${route.params.id}/members`)
122
+ teammates.set(data.data || [])
123
+ }
124
+ catch {
125
+ teammates.set([])
126
+ }
127
+ finally {
128
+ isLoading.set(false)
129
+ }
130
+ }
131
+
132
+ function validateEmail(email: string): boolean {
133
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
134
+ }
135
+
136
+ async function handleInvite(): Promise<void> {
137
+ inviteError.set('')
138
+ inviteSuccess.set('')
139
+
140
+ const email = inviteEmail().trim()
141
+ if (!email) {
142
+ inviteError.set('Please enter an email address.')
143
+ return
144
+ }
145
+ if (!validateEmail(email)) {
146
+ inviteError.set('Please enter a valid email address.')
147
+ return
148
+ }
149
+
150
+ isInviting.set(true)
151
+ try {
152
+ await post(`/teams/${route.params.id}/invite`, { email })
153
+ inviteSuccess.set(`Invitation sent to ${email}.`)
154
+ inviteEmail.set('')
155
+ await fetchTeammates()
156
+ }
157
+ catch (e) {
158
+ inviteError.set(e instanceof Error ? e.message : 'Failed to send invitation.')
159
+ }
160
+ finally {
161
+ isInviting.set(false)
162
+ }
163
+ }
164
+
165
+ onMount(() => {
166
+ fetchTeammates()
167
+ })
168
+ </script>
169
+
170
+ @endsection
@@ -0,0 +1,92 @@
1
+ <script server>
2
+ const category = ''
3
+ const subcategory = ''
4
+ const filter = ''
5
+ </script>
6
+ <ForumLayout :route="routeType">
7
+ <div class="grid gap-8 grid-cols-1 lg:grid-cols-3">
8
+ <div class="lg:col-span-2">
9
+ <ForumBreadcrumb :items="breadcrumbItems" />
10
+
11
+ <div class="mb-6">
12
+ <div class="flex items-center justify-between px-4 py-3 bg-gray-100 rounded-t-md">
13
+ <div>
14
+ <h1 class="font-bold text-xl">{{ categoryTitle }}</h1>
15
+ <p class="mt-1 text-gray-600 text-sm">{{ categoryDescription }}</p>
16
+ </div>
17
+
18
+ <div>
19
+ <a href="/forum/new-topic" class="px-4 py-2 text-white bg-primary rounded-md hover:opacity-90">New Topic</a>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <ForumTopicList :title="listTitle">
25
+ @foreach (topic in topics)
26
+ <ForumTopicItem
27
+ :key="topic.id"
28
+ :title="topic.title"
29
+ :link="topic.link"
30
+ :authorName="topic.authorName"
31
+ :authorAvatar="topic.authorAvatar"
32
+ :createdAt="topic.createdAt"
33
+ :viewCount="topic.viewCount"
34
+ :replyCount="topic.replyCount"
35
+ :likeCount="topic.likeCount"
36
+ :category="topic.category"
37
+ :categoryLink="topic.categoryLink"
38
+ :excerpt="topic.excerpt || ''"
39
+ :isPinned="!!topic.isPinned"
40
+ :isSolved="!!topic.isSolved"
41
+ :lastReplyAuthorName="topic.lastReplyAuthorName || ''"
42
+ :lastReplyAuthorAvatar="topic.lastReplyAuthorAvatar || ''"
43
+ :lastReplyAt="topic.lastReplyAt || ''"
44
+ />
45
+ @endforeach
46
+ </ForumTopicList>
47
+
48
+ <ForumPagination :currentPage="1" :totalPages="5" />
49
+ </div>
50
+
51
+ <div class="lg:col-span-1">
52
+ <ForumSidebar
53
+ :stats="{
54
+ topics: 540,
55
+ posts: 3287,
56
+ members: 1245,
57
+ newestMember: 'NewUser123'
58
+ }"
59
+ :popularTopics="[
60
+ {
61
+ id: 1,
62
+ title: 'How to implement authentication in a new Stacks.js project?',
63
+ link: '/forum/topic/1',
64
+ replyCount: 3,
65
+ viewCount: 45
66
+ },
67
+ {
68
+ id: 2,
69
+ title: 'Best practices for database migrations in Stacks.js',
70
+ link: '/forum/topic/3',
71
+ replyCount: 15,
72
+ viewCount: 128
73
+ },
74
+ {
75
+ id: 3,
76
+ title: 'Introducing myself to the community!',
77
+ link: '/forum/topic/2',
78
+ replyCount: 8,
79
+ viewCount: 32
80
+ }
81
+ ]"
82
+ :onlineUsers="[
83
+ { id: 1, name: 'JohnDoe', avatar: '/images/avatars/default.png' },
84
+ { id: 2, name: 'DatabaseGuru', avatar: '/images/avatars/default.png' },
85
+ { id: 3, name: 'NewUser123', avatar: '/images/avatars/default.png' },
86
+ { id: 4, name: 'ExpertUser', avatar: '/images/avatars/default.png' },
87
+ { id: 5, name: 'StacksMaster', avatar: '/images/avatars/default.png' }
88
+ ]"
89
+ />
90
+ </div>
91
+ </div>
92
+ </ForumLayout>