@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,475 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Reviews')
5
+ <PageLayout>
6
+ <div class="sm:flex sm:items-center sm:justify-between">
7
+ <PageHeader title="Customer Reviews" description="Monitor and respond to customer reviews from Google, Yelp, and other platforms" />
8
+ </div>
9
+
10
+ <div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mt-6">
11
+ <div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
12
+ <div class="p-2 h-10 w-10 bg-blue-100 dark:bg-blue-900 rounded-md">
13
+ <div class="h-6 w-6 text-blue-600 dark:text-blue-300 i-hugeicons-message-multiple-02"></div>
14
+ </div>
15
+ <div class="ml-5">
16
+ <dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Total Reviews</dt>
17
+ <dd class="font-medium text-gray-900 text-lg dark:text-white">{{ totalReviews() }}</dd>
18
+ </div>
19
+ </div>
20
+ <div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
21
+ <div class="p-2 h-10 w-10 bg-yellow-100 dark:bg-yellow-900 rounded-md">
22
+ <div class="h-6 w-6 text-yellow-600 dark:text-yellow-300 i-hugeicons-star"></div>
23
+ </div>
24
+ <div class="ml-5">
25
+ <dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Average Rating</dt>
26
+ <dd class="font-medium text-gray-900 text-lg dark:text-white">{{ averageRating() }}</dd>
27
+ </div>
28
+ </div>
29
+ <div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
30
+ <div class="p-2 h-10 w-10 bg-green-100 dark:bg-green-900 rounded-md">
31
+ <div class="h-6 w-6 text-green-600 dark:text-green-300 i-hugeicons-checkmark-circle-02"></div>
32
+ </div>
33
+ <div class="ml-5">
34
+ <dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Response Rate</dt>
35
+ <dd class="font-medium text-gray-900 text-lg dark:text-white">{{ responseRate() }}%</dd>
36
+ </div>
37
+ </div>
38
+ <div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
39
+ <div class="p-2 h-10 w-10 bg-red-100 dark:bg-red-900 rounded-md">
40
+ <div class="h-6 w-6 text-red-600 dark:text-red-300 i-hugeicons-flag-02"></div>
41
+ </div>
42
+ <div class="ml-5">
43
+ <dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Flagged</dt>
44
+ <dd class="font-medium text-gray-900 text-lg dark:text-white">{{ flaggedCount() }}</dd>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="grid gap-5 grid-cols-1 lg:grid-cols-3 mt-6">
50
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
51
+ <div class="p-6">
52
+ <h3 class="font-medium text-base text-gray-900 dark:text-white">Rating Distribution</h3>
53
+ <div class="mt-4 h-56"><canvas id="reviews_rating_chart"></canvas></div>
54
+ </div>
55
+ </div>
56
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
57
+ <div class="p-6">
58
+ <h3 class="font-medium text-base text-gray-900 dark:text-white">Sentiment Breakdown</h3>
59
+ <div class="mt-4 h-56"><canvas id="reviews_sentiment_chart"></canvas></div>
60
+ </div>
61
+ </div>
62
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
63
+ <div class="p-6">
64
+ <h3 class="font-medium text-base text-gray-900 dark:text-white">By Source</h3>
65
+ <div class="mt-4 h-56"><canvas id="reviews_source_chart"></canvas></div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between mt-8">
71
+ <div class="relative flex-1 max-w-sm">
72
+ <div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
73
+ <div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
74
+ </div>
75
+ <input x-model="searchQuery" type="text" placeholder="Search reviews..." class="block pl-10 pr-3 py-1.5 w-full text-gray-900 sm:leading-6 sm:text-sm dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600" />
76
+ </div>
77
+
78
+ <div class="flex flex-col gap-4 sm:flex-row">
79
+ <select x-model="sourceFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
80
+ <option value="all">All Sources</option>
81
+ <template :for="source in sources">
82
+ <option :value="source">{{ source }}</option>
83
+ </template>
84
+ </select>
85
+ <select x-model="ratingFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
86
+ <option value="all">All Ratings</option>
87
+ <option value="5">5 stars</option>
88
+ <option value="4">4 stars</option>
89
+ <option value="3">3 stars</option>
90
+ <option value="2">2 stars</option>
91
+ <option value="1">1 star</option>
92
+ </select>
93
+ <select x-model="sentimentFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
94
+ <option value="all">All Sentiments</option>
95
+ <template :for="sentiment in sentiments">
96
+ <option :value="sentiment">{{ sentiment }}</option>
97
+ </template>
98
+ </select>
99
+ <select x-model="statusFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
100
+ <option value="all">All Statuses</option>
101
+ <template :for="status in reviewStatuses">
102
+ <option :value="status">{{ status }}</option>
103
+ </template>
104
+ </select>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="mt-6 space-y-4">
109
+ <template :for="review in filteredReviews">
110
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700 shadow-sm">
111
+ <div class="p-6">
112
+ <div class="flex items-start justify-between">
113
+ <div class="flex items-start space-x-4">
114
+ <img :src="review.authorAvatar" :alt="review.author" class="object-cover h-10 w-10 rounded-full" />
115
+ <div>
116
+ <h3 class="font-medium text-base text-gray-900 dark:text-white">{{ review.author }}</h3>
117
+ <div class="flex items-center mt-1 space-x-2">
118
+ <span class="{{ sourceBadgeClass(review.source) }}">{{ review.source }}</span>
119
+ <span class="text-gray-500 text-sm dark:text-neutral-400">{{ formatDate(review.date) }}</span>
120
+ <span :show="review.isVerified" class="inline-flex items-center px-1.5 py-0.5 font-medium text-blue-700 text-xs dark:text-blue-300 bg-blue-100 dark:bg-blue-900 rounded">Verified</span>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <div class="flex flex-col items-end space-y-2">
126
+ <div class="flex items-center">{{ ratingStars(review.rating) }}</div>
127
+ <span class="{{ statusBadgeClass(review.status) }}">{{ review.status }}</span>
128
+ </div>
129
+ </div>
130
+
131
+ <h4 class="mt-3 font-medium text-gray-900 dark:text-white">{{ review.title }}</h4>
132
+ <p class="mt-1 text-gray-700 text-sm dark:text-neutral-300">{{ review.content }}</p>
133
+
134
+ <div :show="review.tags.length > 0" class="flex flex-wrap gap-2 mt-3">
135
+ <template :for="tag in review.tags">
136
+ <span class="inline-flex items-center px-2 py-1 font-medium text-gray-600 text-xs dark:text-neutral-300 bg-gray-50 dark:bg-neutral-700 ring-1 ring-gray-500/10 ring-inset rounded-full">{{ tag }}</span>
137
+ </template>
138
+ </div>
139
+
140
+ <div :show="review.response !== undefined" class="mt-4 p-4 bg-gray-50 dark:bg-neutral-700 border-blue-500 border-l-4 rounded">
141
+ <div class="flex items-center justify-between">
142
+ <span class="font-medium text-gray-900 text-sm dark:text-white">{{ review.response?.author }}</span>
143
+ <span class="text-gray-500 text-xs dark:text-neutral-400">{{ formatDate(review.response?.date) }}</span>
144
+ </div>
145
+ <p class="mt-1 text-gray-700 text-sm dark:text-neutral-300">{{ review.response?.content }}</p>
146
+ </div>
147
+
148
+ <div class="flex items-center justify-between mt-4">
149
+ <div class="flex items-center space-x-4 text-gray-500 text-sm dark:text-neutral-400">
150
+ <span><span class="font-medium">{{ review.helpful }}</span> found helpful</span>
151
+ </div>
152
+
153
+ <div class="flex space-x-2">
154
+ <button @click="openResponseModal(review.id)" class="inline-flex items-center px-2.5 py-1.5 font-semibold text-blue-700 text-sm dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 ring-1 ring-blue-600/20 ring-inset rounded-md shadow-sm">
155
+ <div class="mr-1 h-4 w-4 i-hugeicons-message-edit"></div>
156
+ {{ review.response ? 'Edit Reply' : 'Reply' }}
157
+ </button>
158
+ <button @click="toggleHighlight(review.id)" class="p-1.5 text-gray-700 dark:text-neutral-300 bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm" title="Highlight">
159
+ <div class="h-4 w-4 {{ review.isHighlighted ? 'i-hugeicons-star text-yellow-500' : 'i-hugeicons-star' }}"></div>
160
+ </button>
161
+ <button @click="toggleFlag(review.id)" class="p-1.5 text-gray-700 dark:text-neutral-300 bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm" title="Flag">
162
+ <div class="h-4 w-4 {{ review.status === 'flagged' ? 'i-hugeicons-flag-02 text-red-500' : 'i-hugeicons-flag-02' }}"></div>
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </template>
169
+
170
+ <div :show="filteredReviews().length === 0" class="py-12 text-center">
171
+ <div class="mx-auto h-12 w-12 text-gray-400 i-hugeicons-message-multiple-02"></div>
172
+ <h3 class="mt-2 font-semibold text-gray-900 text-sm dark:text-white">No reviews found</h3>
173
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Try adjusting your search or filters.</p>
174
+ </div>
175
+ </div>
176
+
177
+ <div :show="showResponseModal()" @click.self="closeResponseModal()" class="flex fixed inset-0 z-50 items-center justify-center p-4 bg-black bg-opacity-50">
178
+ <div class="overflow-y-auto p-6 max-h-[90vh] w-full sm:max-w-2xl bg-white dark:bg-neutral-800 rounded-lg shadow-xl">
179
+ <div class="flex items-start justify-between mb-4">
180
+ <h3 class="font-semibold text-gray-900 text-lg dark:text-white">Respond to {{ respondingReview()?.author }}</h3>
181
+ <button @click="closeResponseModal()" class="text-gray-400 dark:text-neutral-300 hover:text-gray-600">
182
+ <i class="h-5 w-5 i-hugeicons-cancel-01"></i>
183
+ </button>
184
+ </div>
185
+
186
+ <div :show="respondingReview() !== null" class="mb-4 p-4 bg-gray-50 dark:bg-neutral-700 rounded">
187
+ <div class="flex items-center mb-1">{{ ratingStars(respondingReview()?.rating ?? 0) }}</div>
188
+ <h4 class="font-medium text-gray-900 text-sm dark:text-white">{{ respondingReview()?.title }}</h4>
189
+ <p class="mt-1 text-gray-700 text-sm dark:text-neutral-300">{{ respondingReview()?.content }}</p>
190
+ </div>
191
+
192
+ <div>
193
+ <label class="block font-medium text-gray-900 text-sm dark:text-white">Your Response</label>
194
+ <textarea x-model="responseText" rows="6" placeholder="Write a thoughtful response..." class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm"></textarea>
195
+ </div>
196
+
197
+ <div class="flex justify-end mt-6 space-x-2">
198
+ <button @click="closeResponseModal()" class="inline-flex justify-center px-3 py-2 font-semibold text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Cancel</button>
199
+ <button @click="submitResponse()" :disabled="!responseText()" class="inline-flex justify-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm disabled:opacity-50">Submit Response</button>
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </PageLayout>
204
+
205
+ <script client>
206
+ // Filter options — moved out of the former `<script server>` block
207
+ // (stacksjs/stacks#1838).
208
+ const sources = ['Google', 'Yelp', 'Facebook', 'TripAdvisor', 'Other']
209
+ const reviewStatuses = ['unread', 'read', 'responded', 'flagged']
210
+ const sentiments = ['positive', 'neutral', 'negative']
211
+
212
+ interface Review {
213
+ id: number
214
+ uuid: string
215
+ source: 'Google' | 'Yelp' | 'Facebook' | 'TripAdvisor' | 'Other'
216
+ rating: number
217
+ title: string
218
+ content: string
219
+ author: string
220
+ authorAvatar?: string
221
+ date: string
222
+ location: string
223
+ response?: { content: string, date: string, author: string }
224
+ status: 'unread' | 'read' | 'responded' | 'flagged'
225
+ sentiment: 'positive' | 'neutral' | 'negative'
226
+ tags: string[]
227
+ helpful: number
228
+ notHelpful: number
229
+ isVerified: boolean
230
+ isHighlighted: boolean
231
+ isHidden: boolean
232
+ }
233
+
234
+ interface ChartLike { destroy: () => void, update: () => void, data: { labels: string[], datasets: Array<{ label?: string, data: number[] }> } }
235
+ type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
236
+
237
+ const initialReviews: Review[] = [
238
+ { id: 1, uuid: '11ce', source: 'Google', rating: 5, title: 'Excellent service and product quality', content: 'I was extremely impressed with both the quality of the product and the customer service.', author: 'Alex Johnson', authorAvatar: 'https://randomuser.me/api/portraits/men/32.jpg', date: '2023-07-15', location: 'Main Street Branch', response: { content: 'Thank you for your kind words, Alex!', date: '2023-07-16', author: 'Customer Support Team' }, status: 'responded', sentiment: 'positive', tags: ['service', 'product quality', 'staff'], helpful: 12, notHelpful: 0, isVerified: true, isHighlighted: true, isHidden: false },
239
+ { id: 2, uuid: '22ce', source: 'Yelp', rating: 2, title: 'Disappointed with wait times', content: 'I had to wait over 45 minutes to be served. The staff seemed disorganized.', author: 'Sarah Miller', authorAvatar: 'https://randomuser.me/api/portraits/women/44.jpg', date: '2023-07-10', location: 'Downtown Location', status: 'read', sentiment: 'negative', tags: ['wait time', 'staff', 'busy'], helpful: 8, notHelpful: 2, isVerified: true, isHighlighted: false, isHidden: false },
240
+ { id: 3, uuid: '33ce', source: 'Google', rating: 4, title: 'Great product, room for improvement', content: 'The product exceeded my expectations. However, pricing could be more competitive.', author: 'David Chen', authorAvatar: 'https://randomuser.me/api/portraits/men/67.jpg', date: '2023-07-05', location: 'Online Store', response: { content: 'Thank you for your feedback, David!', date: '2023-07-06', author: 'Product Team' }, status: 'responded', sentiment: 'positive', tags: ['product quality', 'pricing', 'value'], helpful: 15, notHelpful: 3, isVerified: true, isHighlighted: false, isHidden: false },
241
+ { id: 4, uuid: '44ce', source: 'Yelp', rating: 1, title: 'Terrible experience, avoid at all costs', content: 'The product was defective, and customer service was rude.', author: 'Emily Wilson', authorAvatar: 'https://randomuser.me/api/portraits/women/23.jpg', date: '2023-07-01', location: 'Westside Branch', status: 'flagged', sentiment: 'negative', tags: ['defective product', 'customer service', 'rude staff'], helpful: 20, notHelpful: 1, isVerified: true, isHighlighted: false, isHidden: false },
242
+ { id: 5, uuid: '55ce', source: 'Google', rating: 5, title: 'Outstanding experience from start to finish', content: 'I was treated like a valued customer. The product is amazing.', author: 'Michael Thompson', authorAvatar: 'https://randomuser.me/api/portraits/men/42.jpg', date: '2023-06-28', location: 'Main Street Branch', response: { content: 'Thank you for your wonderful review, Michael!', date: '2023-06-29', author: 'Customer Support Team' }, status: 'responded', sentiment: 'positive', tags: ['service', 'product quality', 'follow-up'], helpful: 18, notHelpful: 0, isVerified: true, isHighlighted: true, isHidden: false },
243
+ { id: 6, uuid: '66ce', source: 'Yelp', rating: 3, title: 'Average experience, nothing special', content: 'The product was okay, and the service was adequate. Nothing stood out.', author: 'Jessica Lee', authorAvatar: 'https://randomuser.me/api/portraits/women/56.jpg', date: '2023-06-25', location: 'Eastside Location', status: 'read', sentiment: 'neutral', tags: ['average', 'basic service'], helpful: 5, notHelpful: 2, isVerified: true, isHighlighted: false, isHidden: false },
244
+ { id: 7, uuid: '77ce', source: 'Google', rating: 4, title: 'Very good but with minor issues', content: 'The staff was friendly and knowledgeable. Slight delay in processing.', author: 'Robert Garcia', authorAvatar: 'https://randomuser.me/api/portraits/men/78.jpg', date: '2023-06-20', location: 'Online Store', status: 'unread', sentiment: 'positive', tags: ['friendly staff', 'knowledgeable', 'delay'], helpful: 9, notHelpful: 1, isVerified: true, isHighlighted: false, isHidden: false },
245
+ { id: 8, uuid: '88ce', source: 'Facebook', rating: 5, title: 'Highly recommend', content: 'My favorite spot in town. Always consistent quality.', author: 'Karen Park', authorAvatar: 'https://randomuser.me/api/portraits/women/12.jpg', date: '2023-06-15', location: 'Main Street Branch', status: 'unread', sentiment: 'positive', tags: ['consistent', 'recommended'], helpful: 6, notHelpful: 0, isVerified: false, isHighlighted: false, isHidden: false },
246
+ { id: 9, uuid: '99ce', source: 'TripAdvisor', rating: 4, title: 'Great find when traveling', content: 'Visited while in town. Will be back next trip.', author: 'James Brown', authorAvatar: 'https://randomuser.me/api/portraits/men/22.jpg', date: '2023-06-10', location: 'Main Street Branch', status: 'read', sentiment: 'positive', tags: ['traveler', 'recommended'], helpful: 4, notHelpful: 0, isVerified: false, isHighlighted: false, isHidden: false },
247
+ ]
248
+
249
+ const reviews = state<Review[]>(initialReviews)
250
+ const searchQuery = state('')
251
+ const sourceFilter = state('all')
252
+ const ratingFilter = state('all')
253
+ const sentimentFilter = state('all')
254
+ const statusFilter = state('all')
255
+
256
+ const showResponseModal = state(false)
257
+ const respondingReviewId = state<number | null>(null)
258
+ const responseText = state('')
259
+
260
+ const totalReviews = derived(() => reviews().length)
261
+ const flaggedCount = derived(() => reviews().filter(r => r.status === 'flagged').length)
262
+ const respondedCount = derived(() => reviews().filter(r => r.response !== undefined).length)
263
+ const responseRate = derived(() => totalReviews() === 0 ? 0 : Math.round((respondedCount() / totalReviews()) * 100))
264
+ const averageRating = derived(() => {
265
+ const total = reviews().reduce((sum, r) => sum + r.rating, 0)
266
+ return totalReviews() === 0 ? '0.0' : (total / totalReviews()).toFixed(1)
267
+ })
268
+
269
+ const ratingDistribution = derived(() => {
270
+ const counts = [0, 0, 0, 0, 0]
271
+ for (const r of reviews()) {
272
+ if (r.rating >= 1 && r.rating <= 5)
273
+ counts[r.rating - 1]!++
274
+ }
275
+ return counts
276
+ })
277
+
278
+ const sentimentDistribution = derived(() => ({
279
+ positive: reviews().filter(r => r.sentiment === 'positive').length,
280
+ neutral: reviews().filter(r => r.sentiment === 'neutral').length,
281
+ negative: reviews().filter(r => r.sentiment === 'negative').length,
282
+ }))
283
+
284
+ const sourceDistribution = derived(() => {
285
+ const sourceList = ['Google', 'Yelp', 'Facebook', 'TripAdvisor', 'Other']
286
+ return sourceList.map(source => ({
287
+ source,
288
+ count: reviews().filter(r => r.source === source).length,
289
+ }))
290
+ })
291
+
292
+ const filteredReviews = derived(() => {
293
+ const q = searchQuery().toLowerCase()
294
+ const source = sourceFilter()
295
+ const rating = ratingFilter()
296
+ const sentiment = sentimentFilter()
297
+ const status = statusFilter()
298
+ return reviews()
299
+ .filter((review) => {
300
+ const matchesSearch = !q
301
+ || review.title.toLowerCase().includes(q)
302
+ || review.content.toLowerCase().includes(q)
303
+ || review.author.toLowerCase().includes(q)
304
+ || review.tags.some(t => t.toLowerCase().includes(q))
305
+ const matchesSource = source === 'all' || review.source === source
306
+ const matchesRating = rating === 'all' || review.rating === Number.parseInt(rating, 10)
307
+ const matchesSentiment = sentiment === 'all' || review.sentiment === sentiment
308
+ const matchesStatus = status === 'all' || review.status === status
309
+ return matchesSearch && matchesSource && matchesRating && matchesSentiment && matchesStatus
310
+ })
311
+ .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
312
+ })
313
+
314
+ const respondingReview = derived(() => reviews().find(r => r.id === respondingReviewId()) ?? null)
315
+
316
+ function formatDate(date: string | null | undefined) {
317
+ if (!date)
318
+ return '-'
319
+ return new Date(date).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
320
+ }
321
+
322
+ function ratingStars(rating: number) {
323
+ return '★'.repeat(rating) + '☆'.repeat(Math.max(0, 5 - rating))
324
+ }
325
+
326
+ function sourceBadgeClass(source: string) {
327
+ const base = 'inline-flex items-center px-2 py-0.5 font-medium text-xs rounded'
328
+ switch (source) {
329
+ case 'Google': return `${base} bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200`
330
+ case 'Yelp': return `${base} bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-200`
331
+ case 'Facebook': return `${base} bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-200`
332
+ case 'TripAdvisor': return `${base} bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200`
333
+ default: return `${base} bg-gray-100 text-gray-700 dark:bg-neutral-700 dark:text-neutral-200`
334
+ }
335
+ }
336
+
337
+ function statusBadgeClass(status: string) {
338
+ const base = 'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset'
339
+ switch (status) {
340
+ case 'unread': return `${base} bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-900/30 dark:text-blue-400`
341
+ case 'read': return `${base} bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400`
342
+ case 'responded': return `${base} bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-900/30 dark:text-green-400`
343
+ case 'flagged': return `${base} bg-red-50 text-red-700 ring-red-600/20 dark:bg-red-900/30 dark:text-red-400`
344
+ default: return `${base} bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400`
345
+ }
346
+ }
347
+
348
+ function openResponseModal(id: number) {
349
+ const review = reviews().find(r => r.id === id)
350
+ if (!review)
351
+ return
352
+ respondingReviewId.set(id)
353
+ responseText.set(review.response?.content ?? '')
354
+ showResponseModal.set(true)
355
+ if (review.status === 'unread')
356
+ reviews.set(reviews().map(r => r.id === id ? { ...r, status: 'read' } : r))
357
+ }
358
+
359
+ function closeResponseModal() {
360
+ showResponseModal.set(false)
361
+ respondingReviewId.set(null)
362
+ responseText.set('')
363
+ }
364
+
365
+ function submitResponse() {
366
+ const id = respondingReviewId()
367
+ const text = responseText().trim()
368
+ if (!id || !text)
369
+ return
370
+ const today = new Date().toISOString().split('T')[0]!
371
+ reviews.set(reviews().map(r => r.id === id
372
+ ? { ...r, response: { content: text, date: today, author: 'Customer Support Team' }, status: 'responded' }
373
+ : r))
374
+ closeResponseModal()
375
+ }
376
+
377
+ function toggleFlag(id: number) {
378
+ reviews.set(reviews().map(r => r.id === id
379
+ ? { ...r, status: r.status === 'flagged' ? 'read' : 'flagged' }
380
+ : r))
381
+ }
382
+
383
+ function toggleHighlight(id: number) {
384
+ reviews.set(reviews().map(r => r.id === id ? { ...r, isHighlighted: !r.isHighlighted } : r))
385
+ }
386
+
387
+ let chartHandles: Array<ChartLike | null> = []
388
+
389
+ function destroyCharts() {
390
+ for (const h of chartHandles)
391
+ h?.destroy()
392
+ chartHandles = []
393
+ }
394
+
395
+ async function buildCharts() {
396
+ destroyCharts()
397
+ const { Chart, registerables } = await import('@stacksjs/charts')
398
+ Chart.register(...registerables)
399
+ const Ctor = Chart as unknown as ChartCtor
400
+
401
+ const baseOptions = {
402
+ responsive: true,
403
+ maintainAspectRatio: false,
404
+ plugins: { legend: { display: false } },
405
+ scales: {
406
+ y: { beginAtZero: true, ticks: { precision: 0 } },
407
+ x: { grid: { display: false } },
408
+ },
409
+ }
410
+
411
+ const ratingChart = useChart({
412
+ Chart: Ctor,
413
+ id: 'reviews_rating_chart',
414
+ type: 'bar',
415
+ data: {
416
+ labels: ['1★', '2★', '3★', '4★', '5★'],
417
+ datasets: [{
418
+ label: 'Reviews',
419
+ data: ratingDistribution(),
420
+ backgroundColor: ['#ef4444', '#f97316', '#eab308', '#84cc16', '#22c55e'],
421
+ }],
422
+ },
423
+ options: baseOptions,
424
+ })
425
+ chartHandles.push(ratingChart.instance as ChartLike | null)
426
+
427
+ const sentiment = sentimentDistribution()
428
+ const sentimentChart = useChart({
429
+ Chart: Ctor,
430
+ id: 'reviews_sentiment_chart',
431
+ type: 'bar',
432
+ data: {
433
+ labels: ['Positive', 'Neutral', 'Negative'],
434
+ datasets: [{
435
+ label: 'Reviews',
436
+ data: [sentiment.positive, sentiment.neutral, sentiment.negative],
437
+ backgroundColor: ['#22c55e', '#3b82f6', '#ef4444'],
438
+ }],
439
+ },
440
+ options: baseOptions,
441
+ })
442
+ chartHandles.push(sentimentChart.instance as ChartLike | null)
443
+
444
+ const sourceData = sourceDistribution()
445
+ const sourceChart = useChart({
446
+ Chart: Ctor,
447
+ id: 'reviews_source_chart',
448
+ type: 'doughnut',
449
+ data: {
450
+ labels: sourceData.map(s => s.source),
451
+ datasets: [{
452
+ label: 'Reviews',
453
+ data: sourceData.map(s => s.count),
454
+ backgroundColor: ['#3b82f6', '#ef4444', '#8b5cf6', '#22c55e', '#f59e0b'],
455
+ }],
456
+ },
457
+ options: {
458
+ responsive: true,
459
+ maintainAspectRatio: false,
460
+ plugins: { legend: { position: 'bottom' } },
461
+ },
462
+ })
463
+ chartHandles.push(sourceChart.instance as ChartLike | null)
464
+ }
465
+
466
+ onMount(async () => {
467
+ await buildCharts()
468
+ })
469
+
470
+ onDestroy(() => {
471
+ destroyCharts()
472
+ })
473
+ </script>
474
+
475
+ @endsection