@stacksjs/defaults 0.70.206 → 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,169 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5
+ <div class="flex flex-col gap-6 mx-auto max-w-6xl">
6
+ <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
7
+ <div>
8
+ <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9
+ <h1 class="mt-1 font-semibold text-2xl tracking-tight">Categories</h1>
10
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Organize posts and markdown content into public blog categories.</p>
11
+ </div>
12
+ <button id="new-category-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
13
+ <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
14
+ New Category
15
+ </button>
16
+ </header>
17
+
18
+ <section class="grid gap-4 md:grid-cols-3">
19
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
20
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Categories</p>
21
+ <p id="category-total" class="mt-2 font-semibold text-3xl">0</p>
22
+ </div>
23
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
24
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
25
+ <p id="category-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
26
+ </div>
27
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
28
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Active</p>
29
+ <p id="category-active" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
30
+ </div>
31
+ </section>
32
+
33
+ <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34
+ <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
35
+ <input id="category-search" type="search" placeholder="Search categories..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
36
+ </div>
37
+ <div class="overflow-x-auto">
38
+ <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
39
+ <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
40
+ <tr>
41
+ <th class="px-4 py-3">Name</th>
42
+ <th class="px-4 py-3">Slug</th>
43
+ <th class="px-4 py-3">Description</th>
44
+ <th class="px-4 py-3 text-right">Actions</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody id="categories-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
48
+ </table>
49
+ </div>
50
+ <div id="categories-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No categories found.</div>
51
+ </section>
52
+ </div>
53
+ </div>
54
+
55
+ <dialog id="category-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
56
+ <form id="category-form" method="dialog">
57
+ <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
58
+ <h2 class="font-semibold text-lg">New Category</h2>
59
+ </div>
60
+ <div class="grid gap-4 p-5">
61
+ <label class="grid gap-1 font-medium text-sm">Name <input id="category-name" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
62
+ <label class="grid gap-1 font-medium text-sm">Slug <input id="category-slug" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
63
+ <label class="grid gap-1 font-medium text-sm">Description <textarea id="category-description" rows="3" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700"></textarea></label>
64
+ </div>
65
+ <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
66
+ <button id="category-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
67
+ <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Category</button>
68
+ </div>
69
+ </form>
70
+ </dialog>
71
+
72
+ <script>
73
+ ;(function () {
74
+ var api = '/api/dashboard/categories'
75
+ var categories = []
76
+ var table = document.getElementById('categories-table')
77
+ var empty = document.getElementById('categories-empty')
78
+ var dialog = document.getElementById('category-dialog')
79
+ var search = document.getElementById('category-search')
80
+ function $(id) { return document.getElementById(id) }
81
+ // CSRF is default-on for POST/PATCH/DELETE via a stateless double-submit
82
+ // cookie: the server seeds a non-HttpOnly X-CSRF-Token cookie on safe
83
+ // responses and the client echoes it back in the header. The list request on
84
+ // page load seeds it, so the token is present by the time a save can happen.
85
+ // See defaults/app/Middleware/Csrf.ts.
86
+ function writeHeaders() {
87
+ var headers = { 'content-type': 'application/json' }
88
+ var match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
89
+ if (match) headers['X-CSRF-Token'] = decodeURIComponent(match[1])
90
+ return headers
91
+ }
92
+ function escapeHtml(value) {
93
+ return String(value || '').replace(/[&<>"']/g, function (ch) {
94
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]
95
+ })
96
+ }
97
+ function slug(value) {
98
+ return String(value || '').trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
99
+ }
100
+ function render() {
101
+ var q = search.value.toLowerCase()
102
+ var rows = categories.filter(function (category) {
103
+ return !q || [category.name, category.slug, category.description].join(' ').toLowerCase().indexOf(q) !== -1
104
+ })
105
+ $('category-total').textContent = String(categories.length)
106
+ $('category-active').textContent = String(categories.filter(function (c) { return c.is_active !== false && c.is_active !== 0 }).length)
107
+ $('category-newest').textContent = categories[0] ? categories[0].name : '-'
108
+ empty.classList.toggle('hidden', rows.length > 0)
109
+ table.innerHTML = rows.map(function (category) {
110
+ return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
111
+ '<td class="px-4 py-3 font-medium">' + escapeHtml(category.name) + '</td>' +
112
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(category.slug) + '</td>' +
113
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(category.description) + '</td>' +
114
+ '<td class="px-4 py-3 text-right"><button type="button" data-id="' + category.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
115
+ '</tr>'
116
+ }).join('')
117
+ }
118
+ async function load() {
119
+ var res = await fetch(api)
120
+ if (!res.ok) throw new Error('Unable to load CMS categories (' + res.status + ').')
121
+ var json = await res.json()
122
+ categories = Array.isArray(json.categories) ? json.categories : []
123
+ render()
124
+ }
125
+ document.getElementById('new-category-button').addEventListener('click', function () {
126
+ $('category-name').value = ''
127
+ $('category-slug').value = ''
128
+ $('category-description').value = ''
129
+ dialog.showModal()
130
+ })
131
+ $('category-cancel').addEventListener('click', function () { dialog.close() })
132
+ $('category-name').addEventListener('input', function () {
133
+ if (!$('category-slug').dataset.touched) $('category-slug').value = slug($('category-name').value)
134
+ })
135
+ $('category-slug').addEventListener('input', function () { $('category-slug').dataset.touched = '1' })
136
+ document.getElementById('category-form').addEventListener('submit', async function (event) {
137
+ event.preventDefault()
138
+ var res = await fetch(api, {
139
+ method: 'POST',
140
+ headers: writeHeaders(),
141
+ body: JSON.stringify({ name: $('category-name').value, slug: $('category-slug').value, description: $('category-description').value }),
142
+ })
143
+ if (!res.ok) {
144
+ var err = await res.json().catch(function () { return {} })
145
+ return alert(err.message || err.error || 'Could not create category')
146
+ }
147
+ dialog.close()
148
+ await load()
149
+ })
150
+ table.addEventListener('click', async function (event) {
151
+ var button = event.target.closest('button[data-id]')
152
+ if (!button || !confirm('Delete this category?')) return
153
+ var res = await fetch(api + '/' + button.getAttribute('data-id'), { method: 'DELETE', headers: writeHeaders() })
154
+ if (!res.ok) {
155
+ var err = await res.json().catch(function () { return {} })
156
+ return alert(err.message || err.error || 'Could not delete category')
157
+ }
158
+ await load()
159
+ })
160
+ search.addEventListener('input', render)
161
+
162
+ load().catch(function (error) {
163
+ empty.classList.remove('hidden')
164
+ empty.textContent = error.message || 'Unable to load CMS categories.'
165
+ })
166
+ })()
167
+ </script>
168
+
169
+ @endsection
@@ -0,0 +1,189 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5
+ <div class="flex flex-col gap-6 mx-auto max-w-7xl">
6
+ <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
7
+ <div>
8
+ <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9
+ <h1 class="mt-1 font-semibold text-2xl tracking-tight">Comments</h1>
10
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Moderate reader comments from the CMS comments table.</p>
11
+ </div>
12
+ <div class="flex flex-wrap gap-2">
13
+ <select id="comment-status-filter" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
14
+ <option value="">All statuses</option>
15
+ <option value="approved">Approved</option>
16
+ <option value="pending">Pending</option>
17
+ <option value="spam">Spam</option>
18
+ <option value="trash">Trash</option>
19
+ </select>
20
+ </div>
21
+ </header>
22
+
23
+ <section class="grid gap-4 md:grid-cols-4">
24
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
25
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Comments</p>
26
+ <p id="comment-total" class="mt-2 font-semibold text-3xl">0</p>
27
+ </div>
28
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
29
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Approved</p>
30
+ <p id="comment-approved" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
31
+ </div>
32
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
33
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Pending</p>
34
+ <p id="comment-pending" class="mt-2 font-semibold text-3xl text-amber-600 dark:text-amber-400">0</p>
35
+ </div>
36
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
37
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Spam</p>
38
+ <p id="comment-spam" class="mt-2 font-semibold text-3xl text-red-600 dark:text-red-400">0</p>
39
+ </div>
40
+ </section>
41
+
42
+ <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
43
+ <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
44
+ <input id="comment-search" type="search" placeholder="Search comments..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
45
+ </div>
46
+ <div class="overflow-x-auto">
47
+ <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
48
+ <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
49
+ <tr>
50
+ <th class="px-4 py-3">Author</th>
51
+ <th class="px-4 py-3">Comment</th>
52
+ <th class="px-4 py-3">Post</th>
53
+ <th class="px-4 py-3">Status</th>
54
+ <th class="px-4 py-3">Created</th>
55
+ <th class="px-4 py-3 text-right">Actions</th>
56
+ </tr>
57
+ </thead>
58
+ <tbody id="comments-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
59
+ </table>
60
+ </div>
61
+ <div id="comments-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No comments found.</div>
62
+ </section>
63
+ </div>
64
+ </div>
65
+
66
+ <script>
67
+ ;(function () {
68
+ var api = '/api/dashboard/comments'
69
+ var comments = []
70
+ var table = document.getElementById('comments-table')
71
+ var empty = document.getElementById('comments-empty')
72
+ var search = document.getElementById('comment-search')
73
+ var statusFilter = document.getElementById('comment-status-filter')
74
+
75
+ function $(id) { return document.getElementById(id) }
76
+ // CSRF is default-on for POST/PATCH/DELETE via a stateless double-submit
77
+ // cookie: the server seeds a non-HttpOnly X-CSRF-Token cookie on safe
78
+ // responses and the client echoes it back in the header. The list request on
79
+ // page load seeds it, so the token is present by the time a moderation write
80
+ // can happen. See defaults/app/Middleware/Csrf.ts.
81
+ function writeHeaders() {
82
+ var headers = { 'content-type': 'application/json' }
83
+ var match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
84
+ if (match) headers['X-CSRF-Token'] = decodeURIComponent(match[1])
85
+ return headers
86
+ }
87
+ function escapeHtml(value) {
88
+ return String(value || '').replace(/[&<>"']/g, function (ch) {
89
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]
90
+ })
91
+ }
92
+ function formatDate(value) {
93
+ if (!value) return '-'
94
+ return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date(value))
95
+ }
96
+ function truncate(value, max) {
97
+ value = String(value || '')
98
+ return value.length > max ? value.slice(0, max) + '...' : value
99
+ }
100
+ function normalize(row) {
101
+ return {
102
+ id: row.id,
103
+ content: row.content || row.body || '',
104
+ authorName: row.author_name || row.authorName || row.author || '',
105
+ authorEmail: row.author_email || row.authorEmail || row.email || '',
106
+ postTitle: row.post_title || row.postTitle || row.title || '',
107
+ status: row.status || (row.is_approved || row.isApproved ? 'approved' : 'pending'),
108
+ createdAt: row.created_at || row.createdAt || '',
109
+ }
110
+ }
111
+ function statusClass(status) {
112
+ if (status === 'approved') return 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200'
113
+ if (status === 'spam' || status === 'trash') return 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-200'
114
+ return 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-200'
115
+ }
116
+ function render() {
117
+ var q = search.value.toLowerCase()
118
+ var status = statusFilter.value
119
+ var rows = comments.filter(function (comment) {
120
+ var haystack = [comment.content, comment.authorName, comment.authorEmail, comment.postTitle, comment.status].join(' ').toLowerCase()
121
+ return (!status || comment.status === status) && (!q || haystack.indexOf(q) !== -1)
122
+ })
123
+ $('comment-total').textContent = String(comments.length)
124
+ $('comment-approved').textContent = String(comments.filter(function (c) { return c.status === 'approved' }).length)
125
+ $('comment-pending').textContent = String(comments.filter(function (c) { return c.status === 'pending' }).length)
126
+ $('comment-spam').textContent = String(comments.filter(function (c) { return c.status === 'spam' }).length)
127
+ empty.classList.toggle('hidden', rows.length > 0)
128
+ table.innerHTML = rows.map(function (comment) {
129
+ return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
130
+ '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(comment.authorName) + '</div><div class="text-neutral-500 text-xs">' + escapeHtml(comment.authorEmail) + '</div></td>' +
131
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(truncate(comment.content, 120)) + '</td>' +
132
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(comment.postTitle || '-') + '</td>' +
133
+ '<td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 font-medium text-xs rounded-full ' + statusClass(comment.status) + '">' + escapeHtml(comment.status) + '</span></td>' +
134
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + formatDate(comment.createdAt) + '</td>' +
135
+ '<td class="px-4 py-3 text-right"><div class="flex gap-1 justify-end">' +
136
+ '<button type="button" data-action="approved" data-id="' + comment.id + '" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-checkmark-circle-02"></span></button>' +
137
+ '<button type="button" data-action="spam" data-id="' + comment.id + '" class="p-2 text-amber-600 dark:text-amber-400 hover:bg-amber-50 dark:hover:bg-amber-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-alert-02"></span></button>' +
138
+ '<button type="button" data-action="delete" data-id="' + comment.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
139
+ '</div></td>' +
140
+ '</tr>'
141
+ }).join('')
142
+ }
143
+ async function load() {
144
+ var res = await fetch(api)
145
+ if (!res.ok) throw new Error('Unable to load CMS comments (' + res.status + ').')
146
+ var json = await res.json()
147
+ comments = Array.isArray(json.comments) ? json.comments.map(normalize) : []
148
+ render()
149
+ }
150
+ async function updateStatus(id, status) {
151
+ var res = await fetch(api + '/' + id, {
152
+ method: 'PATCH',
153
+ headers: writeHeaders(),
154
+ body: JSON.stringify({ status: status }),
155
+ })
156
+ if (!res.ok) {
157
+ var err = await res.json().catch(function () { return {} })
158
+ alert(err.message || err.error || 'Could not update comment')
159
+ return
160
+ }
161
+ await load()
162
+ }
163
+ async function deleteComment(id) {
164
+ if (!confirm('Delete this comment?')) return
165
+ var res = await fetch(api + '/' + id, { method: 'DELETE', headers: writeHeaders() })
166
+ if (!res.ok) {
167
+ var err = await res.json().catch(function () { return {} })
168
+ alert(err.message || err.error || 'Could not delete comment')
169
+ return
170
+ }
171
+ await load()
172
+ }
173
+ table.addEventListener('click', function (event) {
174
+ var button = event.target.closest('button[data-action]')
175
+ if (!button) return
176
+ if (button.dataset.action === 'delete') deleteComment(button.dataset.id)
177
+ else updateStatus(button.dataset.id, button.dataset.action)
178
+ })
179
+ search.addEventListener('input', render)
180
+ statusFilter.addEventListener('change', render)
181
+
182
+ load().catch(function (error) {
183
+ empty.classList.remove('hidden')
184
+ empty.textContent = error.message || 'Unable to load CMS comments.'
185
+ })
186
+ })()
187
+ </script>
188
+
189
+ @endsection