@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,174 @@
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">Pages</h1>
10
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Manage static CMS pages alongside your blog content.</p>
11
+ </div>
12
+ <button id="new-page-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 Page
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 Pages</p>
21
+ <p id="page-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">Views</p>
25
+ <p id="page-views" class="mt-2 font-semibold text-3xl">0</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">Newest</p>
29
+ <p id="page-newest" class="mt-2 font-semibold text-2xl truncate">-</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="page-search" type="search" placeholder="Search pages..." 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">Title</th>
42
+ <th class="px-4 py-3">Template</th>
43
+ <th class="px-4 py-3">Views</th>
44
+ <th class="px-4 py-3">Published</th>
45
+ <th class="px-4 py-3 text-right">Actions</th>
46
+ </tr>
47
+ </thead>
48
+ <tbody id="pages-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
49
+ </table>
50
+ </div>
51
+ <div id="pages-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No pages found.</div>
52
+ </section>
53
+ </div>
54
+ </div>
55
+
56
+ <dialog id="page-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">
57
+ <form id="page-form" method="dialog">
58
+ <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
59
+ <h2 class="font-semibold text-lg">New Page</h2>
60
+ </div>
61
+ <div class="grid gap-4 p-5">
62
+ <label class="grid gap-1 font-medium text-sm">Title <input id="page-title" 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>
63
+ <label class="grid gap-1 font-medium text-sm">Template
64
+ <select id="page-template" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
65
+ <option value="default">Default</option>
66
+ <option value="landing">Landing</option>
67
+ <option value="blog">Blog</option>
68
+ <option value="contact">Contact</option>
69
+ </select>
70
+ </label>
71
+ </div>
72
+ <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
73
+ <button id="page-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
74
+ <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Page</button>
75
+ </div>
76
+ </form>
77
+ </dialog>
78
+
79
+ <script>
80
+ ;(function () {
81
+ var api = '/api/dashboard/pages'
82
+ var pages = []
83
+ var table = document.getElementById('pages-table')
84
+ var empty = document.getElementById('pages-empty')
85
+ var dialog = document.getElementById('page-dialog')
86
+ var search = document.getElementById('page-search')
87
+ function $(id) { return document.getElementById(id) }
88
+ // CSRF is default-on for POST/PATCH/DELETE via a stateless double-submit
89
+ // cookie: the server seeds a non-HttpOnly X-CSRF-Token cookie on safe
90
+ // responses and the client echoes it back in the header. The list request on
91
+ // page load seeds it, so the token is present by the time a save can happen.
92
+ // See defaults/app/Middleware/Csrf.ts.
93
+ function writeHeaders() {
94
+ var headers = { 'content-type': 'application/json' }
95
+ var match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
96
+ if (match) headers['X-CSRF-Token'] = decodeURIComponent(match[1])
97
+ return headers
98
+ }
99
+ function escapeHtml(value) {
100
+ return String(value || '').replace(/[&<>"']/g, function (ch) {
101
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]
102
+ })
103
+ }
104
+ function fmtDate(value) {
105
+ if (!value) return '-'
106
+ var date = new Date(value)
107
+ return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleDateString()
108
+ }
109
+ function render() {
110
+ var q = search.value.toLowerCase()
111
+ var rows = pages.filter(function (page) {
112
+ return !q || [page.title, page.template].join(' ').toLowerCase().indexOf(q) !== -1
113
+ })
114
+ $('page-total').textContent = String(pages.length)
115
+ $('page-views').textContent = pages.reduce(function (sum, p) { return sum + Number(p.views || 0) }, 0).toLocaleString()
116
+ $('page-newest').textContent = pages[0] ? pages[0].title : '-'
117
+ empty.classList.toggle('hidden', rows.length > 0)
118
+ table.innerHTML = rows.map(function (page) {
119
+ return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
120
+ '<td class="px-4 py-3 font-medium">' + escapeHtml(page.title) + '</td>' +
121
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(page.template) + '</td>' +
122
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + Number(page.views || 0).toLocaleString() + '</td>' +
123
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + fmtDate(page.published_at || page.created_at) + '</td>' +
124
+ '<td class="px-4 py-3 text-right"><button type="button" data-id="' + page.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>' +
125
+ '</tr>'
126
+ }).join('')
127
+ }
128
+ async function load() {
129
+ var res = await fetch(api)
130
+ if (!res.ok) throw new Error('Unable to load CMS pages (' + res.status + ').')
131
+ var json = await res.json()
132
+ pages = Array.isArray(json.pages) ? json.pages : []
133
+ render()
134
+ }
135
+ document.getElementById('new-page-button').addEventListener('click', function () {
136
+ $('page-title').value = ''
137
+ $('page-template').value = 'default'
138
+ dialog.showModal()
139
+ })
140
+ $('page-cancel').addEventListener('click', function () { dialog.close() })
141
+ document.getElementById('page-form').addEventListener('submit', async function (event) {
142
+ event.preventDefault()
143
+ var res = await fetch(api, {
144
+ method: 'POST',
145
+ headers: writeHeaders(),
146
+ body: JSON.stringify({ title: $('page-title').value, template: $('page-template').value }),
147
+ })
148
+ if (!res.ok) {
149
+ var err = await res.json().catch(function () { return {} })
150
+ return alert(err.message || err.error || 'Could not create page')
151
+ }
152
+ dialog.close()
153
+ await load()
154
+ })
155
+ table.addEventListener('click', async function (event) {
156
+ var button = event.target.closest('button[data-id]')
157
+ if (!button || !confirm('Delete this page?')) return
158
+ var res = await fetch(api + '/' + button.getAttribute('data-id'), { method: 'DELETE', headers: writeHeaders() })
159
+ if (!res.ok) {
160
+ var err = await res.json().catch(function () { return {} })
161
+ return alert(err.message || err.error || 'Could not delete page')
162
+ }
163
+ await load()
164
+ })
165
+ search.addEventListener('input', render)
166
+
167
+ load().catch(function (error) {
168
+ empty.classList.remove('hidden')
169
+ empty.textContent = error.message || 'Unable to load CMS pages.'
170
+ })
171
+ })()
172
+ </script>
173
+
174
+ @endsection
@@ -0,0 +1,269 @@
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">Posts</h1>
10
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Create, edit, publish, and remove content backed by the Stacks CMS tables.</p>
11
+ </div>
12
+ <div class="flex flex-wrap gap-2">
13
+ <a href="/blog" target="_blank" class="inline-flex gap-2 items-center px-3 py-2 font-medium text-sm hover:bg-white dark:hover:bg-neutral-900 border border-neutral-300 rounded-md dark:border-neutral-700">
14
+ <span class="h-4 w-4 i-hugeicons-view"></span>
15
+ View Blog
16
+ </a>
17
+ <button id="new-post-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">
18
+ <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
19
+ New Post
20
+ </button>
21
+ </div>
22
+ </header>
23
+
24
+ <section class="grid gap-4 md:grid-cols-4">
25
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
26
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Posts</p>
27
+ <p id="stat-total" class="mt-2 font-semibold text-3xl">0</p>
28
+ </div>
29
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
30
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Published</p>
31
+ <p id="stat-published" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
32
+ </div>
33
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Drafts</p>
35
+ <p id="stat-drafts" class="mt-2 font-semibold text-3xl text-amber-600 dark:text-amber-400">0</p>
36
+ </div>
37
+ <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
38
+ <p class="text-neutral-500 text-sm dark:text-neutral-400">Views</p>
39
+ <p id="stat-views" class="mt-2 font-semibold text-3xl">0</p>
40
+ </div>
41
+ </section>
42
+
43
+ <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
44
+ <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between p-4 border-b border-neutral-200 dark:border-neutral-800">
45
+ <div class="relative flex-1 max-w-md">
46
+ <span class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
47
+ <input id="post-search" type="search" placeholder="Search posts..." class="pl-9 pr-3 py-2 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" />
48
+ </div>
49
+ <select id="post-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">
50
+ <option value="">All statuses</option>
51
+ <option value="published">Published</option>
52
+ <option value="draft">Draft</option>
53
+ <option value="archived">Archived</option>
54
+ </select>
55
+ </div>
56
+
57
+ <div class="overflow-x-auto">
58
+ <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
59
+ <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
60
+ <tr>
61
+ <th class="px-4 py-3">Title</th>
62
+ <th class="px-4 py-3">Status</th>
63
+ <th class="px-4 py-3">Views</th>
64
+ <th class="px-4 py-3">Published</th>
65
+ <th class="px-4 py-3 text-right">Actions</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody id="posts-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
69
+ </table>
70
+ </div>
71
+
72
+ <div id="posts-empty" class="hidden p-10 text-center">
73
+ <div class="mx-auto h-10 w-10 text-neutral-400 i-hugeicons-quill-write-02"></div>
74
+ <h2 class="mt-3 font-semibold text-sm">No posts found</h2>
75
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Create a draft to get the CMS moving.</p>
76
+ </div>
77
+ </section>
78
+ </div>
79
+ </div>
80
+
81
+ <dialog id="post-dialog" class="p-0 max-w-3xl 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">
82
+ <form id="post-form" method="dialog" class="flex flex-col">
83
+ <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
84
+ <h2 id="post-dialog-title" class="font-semibold text-lg">New Post</h2>
85
+ <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Markdown works well here; the public blog reads from the same content field.</p>
86
+ </div>
87
+ <div class="grid gap-4 p-5">
88
+ <input id="post-id" type="hidden" />
89
+ <label class="grid gap-1 font-medium text-sm">
90
+ Title
91
+ <input id="post-title" required class="px-3 py-2 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" />
92
+ </label>
93
+ <label class="grid gap-1 font-medium text-sm">
94
+ Excerpt
95
+ <textarea id="post-excerpt" rows="2" class="px-3 py-2 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"></textarea>
96
+ </label>
97
+ <div class="grid gap-4 md:grid-cols-2">
98
+ <label class="grid gap-1 font-medium text-sm">
99
+ Status
100
+ <select id="post-status" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
101
+ <option value="draft">Draft</option>
102
+ <option value="published">Published</option>
103
+ <option value="archived">Archived</option>
104
+ </select>
105
+ </label>
106
+ <label class="grid gap-1 font-medium text-sm">
107
+ Poster URL
108
+ <input id="post-poster" class="px-3 py-2 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" />
109
+ </label>
110
+ </div>
111
+ <label class="grid gap-1 font-medium text-sm">
112
+ Content
113
+ <textarea id="post-content" rows="12" class="px-3 py-2 font-mono 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"></textarea>
114
+ </label>
115
+ </div>
116
+ <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
117
+ <button id="post-cancel" type="button" class="px-3 py-2 font-medium text-sm hover:bg-neutral-50 dark:hover:bg-neutral-800 border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
118
+ <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Save Post</button>
119
+ </div>
120
+ </form>
121
+ </dialog>
122
+
123
+ <script>
124
+ ;(function () {
125
+ var api = '/api/dashboard/posts'
126
+ var posts = []
127
+ var table = document.getElementById('posts-table')
128
+ var empty = document.getElementById('posts-empty')
129
+ var dialog = document.getElementById('post-dialog')
130
+ var form = document.getElementById('post-form')
131
+ var search = document.getElementById('post-search')
132
+ var statusFilter = document.getElementById('post-status-filter')
133
+
134
+ function $(id) { return document.getElementById(id) }
135
+ // CSRF is default-on for POST/PATCH/DELETE via a stateless double-submit
136
+ // cookie: the server seeds a non-HttpOnly X-CSRF-Token cookie on safe
137
+ // responses and the client echoes it back in the header. The list request on
138
+ // page load seeds it, so the token is present by the time a save can happen.
139
+ // See defaults/app/Middleware/Csrf.ts.
140
+ function writeHeaders() {
141
+ var headers = { 'content-type': 'application/json' }
142
+ var match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
143
+ if (match) headers['X-CSRF-Token'] = decodeURIComponent(match[1])
144
+ return headers
145
+ }
146
+ function text(value) { return value == null || value === '' ? '-' : String(value) }
147
+ function statusClass(status) {
148
+ if (status === 'published') return 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200'
149
+ if (status === 'archived') return 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300'
150
+ return 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-200'
151
+ }
152
+ function fmtDate(value) {
153
+ if (!value) return '-'
154
+ var date = new Date(value)
155
+ if (Number.isNaN(date.getTime())) return String(value)
156
+ return date.toLocaleDateString()
157
+ }
158
+ function escapeHtml(value) {
159
+ return String(value || '').replace(/[&<>"']/g, function (ch) {
160
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]
161
+ })
162
+ }
163
+ function filteredPosts() {
164
+ var q = (search.value || '').toLowerCase()
165
+ var s = statusFilter.value
166
+ return posts.filter(function (post) {
167
+ var haystack = [post.title, post.excerpt, post.content].join(' ').toLowerCase()
168
+ return (!q || haystack.indexOf(q) !== -1) && (!s || post.status === s)
169
+ })
170
+ }
171
+ function renderStats() {
172
+ var published = posts.filter(function (p) { return p.status === 'published' }).length
173
+ var drafts = posts.filter(function (p) { return p.status === 'draft' }).length
174
+ var views = posts.reduce(function (sum, p) { return sum + Number(p.views || 0) }, 0)
175
+ $('stat-total').textContent = String(posts.length)
176
+ $('stat-published').textContent = String(published)
177
+ $('stat-drafts').textContent = String(drafts)
178
+ $('stat-views').textContent = views.toLocaleString()
179
+ }
180
+ function render() {
181
+ renderStats()
182
+ var rows = filteredPosts()
183
+ empty.classList.toggle('hidden', rows.length > 0)
184
+ table.innerHTML = rows.map(function (post) {
185
+ return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
186
+ '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(post.title) + '</div><div class="mt-1 max-w-xl text-neutral-500 text-xs truncate dark:text-neutral-400">' + escapeHtml(post.excerpt || post.content || '') + '</div></td>' +
187
+ '<td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 font-medium text-xs rounded-full ' + statusClass(post.status) + '">' + escapeHtml(post.status || 'draft') + '</span></td>' +
188
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + Number(post.views || 0).toLocaleString() + '</td>' +
189
+ '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + fmtDate(post.published_at || post.created_at) + '</td>' +
190
+ '<td class="px-4 py-3"><div class="flex gap-1 justify-end">' +
191
+ '<button type="button" class="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded" data-action="edit" data-id="' + post.id + '" title="Edit"><span class="block h-4 w-4 i-hugeicons-edit-01"></span></button>' +
192
+ '<button type="button" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded" data-action="delete" data-id="' + post.id + '" title="Delete"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
193
+ '</div></td>' +
194
+ '</tr>'
195
+ }).join('')
196
+ }
197
+ async function load() {
198
+ var res = await fetch(api)
199
+ if (!res.ok) throw new Error('Unable to load CMS posts (' + res.status + ').')
200
+ var json = await res.json()
201
+ posts = Array.isArray(json.posts) ? json.posts : []
202
+ render()
203
+ }
204
+ function openPost(post) {
205
+ $('post-dialog-title').textContent = post ? 'Edit Post' : 'New Post'
206
+ $('post-id').value = post ? post.id : ''
207
+ $('post-title').value = post ? post.title || '' : ''
208
+ $('post-excerpt').value = post ? post.excerpt || '' : ''
209
+ $('post-status').value = post ? post.status || 'draft' : 'draft'
210
+ $('post-poster').value = post ? post.poster || '' : ''
211
+ $('post-content').value = post ? post.content || '' : ''
212
+ dialog.showModal()
213
+ }
214
+ async function savePost(event) {
215
+ event.preventDefault()
216
+ var id = $('post-id').value
217
+ var payload = {
218
+ title: $('post-title').value,
219
+ excerpt: $('post-excerpt').value,
220
+ status: $('post-status').value,
221
+ poster: $('post-poster').value,
222
+ content: $('post-content').value,
223
+ }
224
+ var res = await fetch(id ? api + '/' + id : api, {
225
+ method: id ? 'PATCH' : 'POST',
226
+ headers: writeHeaders(),
227
+ body: JSON.stringify(payload),
228
+ })
229
+ if (!res.ok) {
230
+ var err = await res.json().catch(function () { return {} })
231
+ alert(err.message || err.error || 'Could not save post')
232
+ return
233
+ }
234
+ dialog.close()
235
+ await load()
236
+ }
237
+ async function deletePost(id) {
238
+ if (!confirm('Delete this post?')) return
239
+ var res = await fetch(api + '/' + id, { method: 'DELETE', headers: writeHeaders() })
240
+ if (!res.ok) {
241
+ alert('Could not delete post')
242
+ return
243
+ }
244
+ await load()
245
+ }
246
+
247
+ document.getElementById('new-post-button').addEventListener('click', function () { openPost(null) })
248
+ document.getElementById('post-cancel').addEventListener('click', function () { dialog.close() })
249
+ form.addEventListener('submit', savePost)
250
+ search.addEventListener('input', render)
251
+ statusFilter.addEventListener('change', render)
252
+ table.addEventListener('click', function (event) {
253
+ var button = event.target.closest('button[data-action]')
254
+ if (!button) return
255
+ var id = Number(button.getAttribute('data-id'))
256
+ if (button.getAttribute('data-action') === 'edit')
257
+ openPost(posts.find(function (post) { return Number(post.id) === id }))
258
+ if (button.getAttribute('data-action') === 'delete')
259
+ deletePost(id)
260
+ })
261
+
262
+ load().catch(function (error) {
263
+ empty.classList.remove('hidden')
264
+ empty.querySelector('p').textContent = error.message || 'Unable to load CMS posts.'
265
+ })
266
+ })()
267
+ </script>
268
+
269
+ @endsection