@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,656 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Commerce Reviews')
5
+ <PageLayout>
6
+ <!-- Header -->
7
+ <div class="sm:flex sm:items-center sm:justify-between">
8
+ <PageHeader title="Reviews" description="Manage customer reviews for your products" />
9
+ </div>
10
+
11
+ <!-- Summary cards -->
12
+ <div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mt-6">
13
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
14
+ <div class="p-5">
15
+ <div class="flex items-center">
16
+ <div class="flex-shrink-0">
17
+ <div class="p-2 h-10 w-10 bg-blue-100 dark:bg-blue-900 rounded-md">
18
+ <div class="h-6 w-6 text-blue-600 dark:text-blue-300 i-hugeicons-bubble-chat"></div>
19
+ </div>
20
+ </div>
21
+ <div class="flex-1 ml-5 w-0">
22
+ <dl>
23
+ <dt class="font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Total Reviews</dt>
24
+ <dd>
25
+ <div class="font-medium text-gray-900 text-lg dark:text-white">{{ totalReviews() }}</div>
26
+ </dd>
27
+ </dl>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
34
+ <div class="p-5">
35
+ <div class="flex items-center">
36
+ <div class="flex-shrink-0">
37
+ <div class="p-2 h-10 w-10 bg-green-100 dark:bg-green-900 rounded-md">
38
+ <div class="h-6 w-6 text-green-600 dark:text-green-300 i-hugeicons-checkmark-circle-02"></div>
39
+ </div>
40
+ </div>
41
+ <div class="flex-1 ml-5 w-0">
42
+ <dl>
43
+ <dt class="font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Published Reviews</dt>
44
+ <dd>
45
+ <div class="font-medium text-gray-900 text-lg dark:text-white">{{ publishedReviews() }}</div>
46
+ </dd>
47
+ </dl>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
54
+ <div class="p-5">
55
+ <div class="flex items-center">
56
+ <div class="flex-shrink-0">
57
+ <div class="p-2 h-10 w-10 bg-yellow-100 dark:bg-yellow-900 rounded-md">
58
+ <div class="h-6 w-6 text-yellow-600 dark:text-yellow-300 i-hugeicons-star"></div>
59
+ </div>
60
+ </div>
61
+ <div class="flex-1 ml-5 w-0">
62
+ <dl>
63
+ <dt class="font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Average Rating</dt>
64
+ <dd>
65
+ <div class="font-medium text-gray-900 text-lg dark:text-white">{{ averageRating() }} / 5</div>
66
+ </dd>
67
+ </dl>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 rounded-lg shadow">
74
+ <div class="p-5">
75
+ <div class="flex items-center">
76
+ <div class="flex-shrink-0">
77
+ <div class="p-2 h-10 w-10 bg-orange-100 dark:bg-orange-900 rounded-md">
78
+ <div class="h-6 w-6 text-orange-600 dark:text-orange-300 i-hugeicons-clock-01"></div>
79
+ </div>
80
+ </div>
81
+ <div class="flex-1 ml-5 w-0">
82
+ <dl>
83
+ <dt class="font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Pending Reviews</dt>
84
+ <dd>
85
+ <div class="font-medium text-gray-900 text-lg dark:text-white">{{ pendingReviews() }}</div>
86
+ </dd>
87
+ </dl>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Filters and view options -->
95
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between mt-6">
96
+ <div class="relative max-w-sm">
97
+ <div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
98
+ <div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
99
+ </div>
100
+ <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 focus:ring-inset" />
101
+ </div>
102
+
103
+ <div class="flex flex-col gap-4 sm:flex-row">
104
+ <select x-model="productFilter" class="block pl-3 pr-10 py-1.5 w-full 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 focus:ring-inset">
105
+ <option value="all">All Products</option>
106
+ <template :for="product in productList">
107
+ <option value="{{ product.id }}">{{ product.name }}</option>
108
+ </template>
109
+ </select>
110
+
111
+ <select x-model="ratingFilter" class="block pl-3 pr-10 py-1.5 w-full 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 focus:ring-inset">
112
+ <option value="all">All Ratings</option>
113
+ <option value="5">5 Stars</option>
114
+ <option value="4">4 Stars</option>
115
+ <option value="3">3 Stars</option>
116
+ <option value="2">2 Stars</option>
117
+ <option value="1">1 Stars</option>
118
+ </select>
119
+
120
+ <select x-model="statusFilter" class="block pl-3 pr-10 py-1.5 w-full 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 focus:ring-inset">
121
+ <option value="all">All Statuses</option>
122
+ <option value="Published">Published</option>
123
+ <option value="Under Review">Under Review</option>
124
+ <option value="Rejected">Rejected</option>
125
+ <option value="Spam">Spam</option>
126
+ </select>
127
+
128
+ <div class="flex rounded-md shadow-sm">
129
+ <button type="button" @click="setViewMode('detailed')" class="relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold ring-1 ring-inset focus:z-10 {{ viewMode() === 'detailed' ? 'bg-blue-600 text-white ring-blue-600' : 'bg-white text-gray-900 ring-gray-300 hover:bg-gray-50 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:hover:bg-neutral-700' }}">
130
+ <div class="h-5 w-5 i-hugeicons-grid"></div>
131
+ </button>
132
+ <button type="button" @click="setViewMode('compact')" class="relative -ml-px inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold ring-1 ring-inset focus:z-10 {{ viewMode() === 'compact' ? 'bg-blue-600 text-white ring-blue-600' : 'bg-white text-gray-900 ring-gray-300 hover:bg-gray-50 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:hover:bg-neutral-700' }}">
133
+ <div class="h-5 w-5 i-hugeicons-list-view"></div>
134
+ </button>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Sort options -->
140
+ <div class="flex items-center mt-4 text-gray-500 text-sm dark:text-neutral-400">
141
+ <span class="mr-2">Sort by:</span>
142
+ <button @click="toggleSort('dateAdded')" class="flex items-center mr-3 hover:text-gray-700 dark:hover:text-gray-300 {{ sortBy() === 'dateAdded' ? 'font-semibold text-blue-600 dark:text-blue-400' : '' }}">
143
+ Date
144
+ <span :show="sortBy() === 'dateAdded'" class="ml-1">
145
+ <div :show="sortOrder() === 'asc'" class="h-4 w-4 i-hugeicons-arrow-up-01"></div>
146
+ <div :show="sortOrder() === 'desc'" class="h-4 w-4 i-hugeicons-arrow-down-02"></div>
147
+ </span>
148
+ </button>
149
+ <button @click="toggleSort('rating')" class="flex items-center mr-3 hover:text-gray-700 dark:hover:text-gray-300 {{ sortBy() === 'rating' ? 'font-semibold text-blue-600 dark:text-blue-400' : '' }}">
150
+ Rating
151
+ <span :show="sortBy() === 'rating'" class="ml-1">
152
+ <div :show="sortOrder() === 'asc'" class="h-4 w-4 i-hugeicons-arrow-up-01"></div>
153
+ <div :show="sortOrder() === 'desc'" class="h-4 w-4 i-hugeicons-arrow-down-02"></div>
154
+ </span>
155
+ </button>
156
+ <button @click="toggleSort('helpful')" class="flex items-center mr-3 hover:text-gray-700 dark:hover:text-gray-300 {{ sortBy() === 'helpful' ? 'font-semibold text-blue-600 dark:text-blue-400' : '' }}">
157
+ Helpful
158
+ <span :show="sortBy() === 'helpful'" class="ml-1">
159
+ <div :show="sortOrder() === 'asc'" class="h-4 w-4 i-hugeicons-arrow-up-01"></div>
160
+ <div :show="sortOrder() === 'desc'" class="h-4 w-4 i-hugeicons-arrow-down-02"></div>
161
+ </span>
162
+ </button>
163
+ <button @click="toggleSort('customerName')" class="flex items-center hover:text-gray-700 dark:hover:text-gray-300 {{ sortBy() === 'customerName' ? 'font-semibold text-blue-600 dark:text-blue-400' : '' }}">
164
+ Customer
165
+ <span :show="sortBy() === 'customerName'" class="ml-1">
166
+ <div :show="sortOrder() === 'asc'" class="h-4 w-4 i-hugeicons-arrow-up-01"></div>
167
+ <div :show="sortOrder() === 'desc'" class="h-4 w-4 i-hugeicons-arrow-down-02"></div>
168
+ </span>
169
+ </button>
170
+ </div>
171
+
172
+ <!-- Detailed view -->
173
+ <div :show="isDetailed" class="mt-6">
174
+ <div :show="hasNoReviews" class="py-12 text-center">
175
+ <div class="mx-auto h-12 w-12 text-gray-400 i-hugeicons-bubble-chat"></div>
176
+ <h3 class="mt-2 font-semibold text-gray-900 text-sm dark:text-white">No reviews found</h3>
177
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Try adjusting your search or filter to find what you're looking for.</p>
178
+ </div>
179
+ <div :show="hasReviews" class="space-y-6">
180
+ <template :for="review in paginatedReviews">
181
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700 shadow-sm">
182
+ <div class="p-6">
183
+ <div class="flex items-start justify-between">
184
+ <div class="flex items-start space-x-4">
185
+ <img src="{{ review.customerAvatar }}" alt="{{ review.customerName }}" class="object-cover h-12 w-12 rounded-full" />
186
+ <div>
187
+ <h3 class="font-medium text-base text-gray-900 dark:text-white">{{ review.customerName }}</h3>
188
+ <p class="text-gray-500 text-sm dark:text-neutral-400">{{ review.customerEmail }}</p>
189
+ <div class="flex items-center mt-1">
190
+ <span class="text-lg text-yellow-400">{{ getRatingStars(review.rating) }}</span>
191
+ <span class="ml-2 text-gray-500 text-sm dark:text-neutral-400">{{ review.dateAdded }}</span>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="flex flex-col items-end space-y-2">
197
+ <span class="{{ getStatusClass(review.status) }} inline-flex items-center rounded-full px-2 py-1 text-xs font-medium">{{ review.status }}</span>
198
+ <span :show="{{ review.featured ? 'true' : 'false' }}" class="inline-flex items-center px-2 py-1 font-medium text-xs text-yellow-700 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-900/30 ring-1 ring-inset ring-yellow-600/20 rounded-full">Featured</span>
199
+ </div>
200
+ </div>
201
+
202
+ <div class="flex items-center mt-4">
203
+ <img src="{{ review.productImage }}" alt="{{ review.productName }}" class="object-cover h-10 w-10 rounded-md" />
204
+ <div class="ml-3">
205
+ <h4 class="font-medium text-gray-900 text-sm dark:text-white">{{ review.productName }}</h4>
206
+ </div>
207
+ </div>
208
+
209
+ <div class="mt-4">
210
+ <h4 class="font-medium text-base text-gray-900 dark:text-white">{{ review.title }}</h4>
211
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ review.comment }}</p>
212
+ </div>
213
+
214
+ <div class="flex items-center mt-4 text-gray-500 text-sm dark:text-neutral-400">
215
+ <div class="flex items-center">
216
+ <div class="mr-1 h-4 w-4 i-hugeicons-thumbs-up"></div>
217
+ <span>{{ review.helpful }} helpful</span>
218
+ </div>
219
+ <div class="flex items-center ml-4">
220
+ <div class="mr-1 h-4 w-4 i-hugeicons-thumbs-down"></div>
221
+ <span>{{ review.unhelpful }} unhelpful</span>
222
+ </div>
223
+ </div>
224
+
225
+ <div class="flex justify-end mt-4 space-x-3">
226
+ <button @click="toggleFeature({{ review.id }})" class="inline-flex items-center px-2.5 py-1.5 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">
227
+ <div class="mr-1 h-4 w-4 {{ review.featured ? 'i-hugeicons-star-off' : 'i-hugeicons-star' }}"></div>
228
+ {{ review.featured ? 'Unfeature' : 'Feature' }}
229
+ </button>
230
+ <button :show="{{ review.status !== 'Published' ? 'true' : 'false' }}" @click="updateReviewStatus({{ review.id }}, 'Published')" class="inline-flex items-center px-2.5 py-1.5 font-semibold text-green-700 text-sm dark:text-green-400 bg-green-50 dark:bg-green-900/30 hover:bg-green-100 dark:hover:bg-green-900/50 ring-1 ring-green-600/20 ring-inset rounded-md shadow-sm">
231
+ <div class="mr-1 h-4 w-4 i-hugeicons-checkmark-circle-02"></div>
232
+ Approve
233
+ </button>
234
+ <button :show="{{ review.status !== 'Rejected' ? 'true' : 'false' }}" @click="updateReviewStatus({{ review.id }}, 'Rejected')" class="inline-flex items-center px-2.5 py-1.5 font-semibold text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/30 hover:bg-red-100 dark:hover:bg-red-900/50 ring-1 ring-inset ring-red-600/20 rounded-md shadow-sm">
235
+ <div class="mr-1 h-4 w-4 i-hugeicons-cancel-circle"></div>
236
+ Reject
237
+ </button>
238
+ <button @click="openEditReviewModal({{ review.id }})" class="inline-flex items-center px-2.5 py-1.5 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">
239
+ <div class="mr-1 h-4 w-4 i-hugeicons-edit-01"></div>
240
+ Edit
241
+ </button>
242
+ <button @click="deleteReview({{ review.id }})" class="inline-flex items-center px-2.5 py-1.5 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">
243
+ <div class="mr-1 h-4 w-4 i-hugeicons-waste"></div>
244
+ Delete
245
+ </button>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </template>
250
+ </div>
251
+ </div>
252
+
253
+ <!-- Compact view -->
254
+ <div :show="isCompact" class="overflow-hidden mt-6 ring-1 ring-black ring-opacity-5 sm:rounded-lg shadow">
255
+ <table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-700">
256
+ <thead class="bg-gray-50 dark:bg-neutral-700">
257
+ <tr>
258
+ <th scope="col" class="pl-4 pr-3 py-3.5 sm:pl-6 font-semibold text-gray-900 text-left text-sm dark:text-white">Customer</th>
259
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Product</th>
260
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Rating</th>
261
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Review</th>
262
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Status</th>
263
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Date</th>
264
+ <th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Actions</span></th>
265
+ </tr>
266
+ </thead>
267
+ <tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-700">
268
+ <tr :show="hasNoReviews">
269
+ <td colspan="7" class="py-4 text-center text-gray-500 text-sm dark:text-neutral-400">No reviews found. Try adjusting your search or filter.</td>
270
+ </tr>
271
+ <template :for="review in paginatedReviews">
272
+ <tr class="hover:bg-gray-50 dark:hover:bg-neutral-700">
273
+ <td class="pl-4 pr-3 py-4 sm:pl-6 text-sm whitespace-nowrap">
274
+ <div class="flex items-center">
275
+ <div class="flex-shrink-0 h-8 w-8">
276
+ <img src="{{ review.customerAvatar }}" alt="{{ review.customerName }}" class="object-cover h-8 w-8 rounded-full" />
277
+ </div>
278
+ <div class="ml-4">
279
+ <div class="font-medium text-gray-900 dark:text-white">{{ review.customerName }}</div>
280
+ <div class="text-gray-500 text-xs dark:text-neutral-400">{{ review.customerEmail }}</div>
281
+ </div>
282
+ </div>
283
+ </td>
284
+ <td class="px-3 py-4 text-sm whitespace-nowrap">
285
+ <div class="flex items-center">
286
+ <div class="flex-shrink-0 h-8 w-8">
287
+ <img src="{{ review.productImage }}" alt="{{ review.productName }}" class="object-cover h-8 w-8 rounded-md" />
288
+ </div>
289
+ <div class="ml-2 text-gray-900 dark:text-white">{{ review.productName }}</div>
290
+ </div>
291
+ </td>
292
+ <td class="px-3 py-4 text-sm whitespace-nowrap">
293
+ <span class="text-yellow-400">{{ getRatingStars(review.rating) }}</span>
294
+ </td>
295
+ <td class="px-3 py-4 max-w-xs text-gray-500 text-sm dark:text-neutral-400">
296
+ <div class="font-medium text-gray-900 dark:text-white">{{ review.title }}</div>
297
+ <div class="truncate">{{ review.comment }}</div>
298
+ </td>
299
+ <td class="px-3 py-4 text-sm whitespace-nowrap">
300
+ <span class="{{ getStatusClass(review.status) }} inline-flex items-center rounded-full px-2 py-1 text-xs font-medium">{{ review.status }}</span>
301
+ <span :show="{{ review.featured ? 'true' : 'false' }}" class="inline-flex items-center mt-1 px-2 py-1 font-medium text-xs text-yellow-700 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-900/30 ring-1 ring-inset ring-yellow-600/20 rounded-full">Featured</span>
302
+ </td>
303
+ <td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ review.dateAdded }}</td>
304
+ <td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
305
+ <div class="flex justify-end space-x-2">
306
+ <button @click="openEditReviewModal({{ review.id }})" type="button" class="text-gray-400 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 duration-150 transition-colors">
307
+ <div class="h-5 w-5 i-hugeicons-edit-01"></div>
308
+ <span class="sr-only">Edit, {{ review.customerName }}'s review</span>
309
+ </button>
310
+ <button @click="deleteReview({{ review.id }})" type="button" class="text-gray-400 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 duration-150 transition-colors">
311
+ <div class="h-5 w-5 i-hugeicons-waste"></div>
312
+ <span class="sr-only">Delete, {{ review.customerName }}'s review</span>
313
+ </button>
314
+ </div>
315
+ </td>
316
+ </tr>
317
+ </template>
318
+ </tbody>
319
+ </table>
320
+ </div>
321
+
322
+ <!-- Pagination -->
323
+ <div :show="hasMultiplePages" class="flex items-center justify-between mt-6 px-4 py-3 sm:px-6 border-gray-200 border-t dark:border-neutral-700">
324
+ <div class="flex sm:hidden flex-1 justify-between">
325
+ <button @click="previousPage()" class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 dark:border-neutral-600 dark:bg-neutral-800 dark:text-white {{ currentPage() === 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50 dark:hover:bg-neutral-700' }}">Previous</button>
326
+ <button @click="nextPage()" class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 dark:border-neutral-600 dark:bg-neutral-800 dark:text-white {{ currentPage() === totalPages() ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50 dark:hover:bg-neutral-700' }}">Next</button>
327
+ </div>
328
+ <div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
329
+ <div>
330
+ <p class="text-gray-700 text-sm dark:text-neutral-300">
331
+ Showing <span class="font-medium">{{ rangeStart() }}</span> to <span class="font-medium">{{ rangeEnd() }}</span> of <span class="font-medium">{{ totalFilteredReviews() }}</span> results
332
+ </p>
333
+ </div>
334
+ <div>
335
+ <nav class="inline-flex -space-x-px rounded-md shadow-sm isolate" aria-label="Pagination">
336
+ <button @click="previousPage()" class="inline-flex relative focus:z-20 items-center px-2 py-2 text-gray-400 dark:text-neutral-500 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-l-md dark:ring-neutral-600 focus:outline-offset-0 {{ currentPage() === 1 ? 'opacity-50 cursor-not-allowed' : '' }}">
337
+ <span class="sr-only">Previous</span>
338
+ <div class="h-5 w-5 i-hugeicons-arrow-left-01"></div>
339
+ </button>
340
+ <template :for="page in pageNumbers">
341
+ <button @click="changePage({{ page }})" class="relative inline-flex items-center px-4 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 focus:z-20 focus:outline-offset-0 dark:ring-neutral-600 {{ page === currentPage() ? 'bg-blue-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:bg-blue-700' : 'text-gray-900 hover:bg-gray-50 dark:text-white dark:hover:bg-neutral-700' }}">{{ page }}</button>
342
+ </template>
343
+ <button @click="nextPage()" class="inline-flex relative focus:z-20 items-center px-2 py-2 text-gray-400 dark:text-neutral-500 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-r-md dark:ring-neutral-600 focus:outline-offset-0 {{ currentPage() === totalPages() ? 'opacity-50 cursor-not-allowed' : '' }}">
344
+ <span class="sr-only">Next</span>
345
+ <div class="h-5 w-5 i-hugeicons-arrow-right-01"></div>
346
+ </button>
347
+ </nav>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <!-- Edit Review Modal -->
355
+ <div :show="showReviewModal" class="fixed inset-0 overflow-y-auto z-10">
356
+ <div class="flex items-end justify-center sm:items-center p-4 sm:p-0 min-h-full text-center">
357
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75 transition-opacity" @click="closeReviewModal()"></div>
358
+ <div class="overflow-hidden relative pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-800 rounded-lg shadow-xl transition-all transform">
359
+ <div class="absolute right-0 top-0 pr-4 pt-4">
360
+ <button type="button" @click="closeReviewModal()" class="text-gray-400 dark:text-neutral-500 hover:text-gray-500 dark:hover:text-gray-400 bg-white dark:bg-neutral-800 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
361
+ <span class="sr-only">Close</span>
362
+ <div class="h-6 w-6 i-hugeicons-cancel-circle"></div>
363
+ </button>
364
+ </div>
365
+ <div class="sm:flex sm:items-start">
366
+ <div class="mt-3 sm:ml-4 sm:mt-0 w-full text-center sm:text-left">
367
+ <h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-white">Edit Review</h3>
368
+ <div class="mt-4">
369
+ <form @submit.prevent="saveReview()" class="space-y-4">
370
+ <div class="flex items-center justify-between">
371
+ <div class="flex items-center">
372
+ <img src="{{ editCustomerAvatar() }}" alt="{{ editCustomerName() }}" class="object-cover h-10 w-10 rounded-full" />
373
+ <div class="ml-3">
374
+ <p class="font-medium text-gray-900 text-sm dark:text-white">{{ editCustomerName() }}</p>
375
+ <p class="text-gray-500 text-xs dark:text-neutral-400">{{ editCustomerEmail() }}</p>
376
+ </div>
377
+ </div>
378
+ <div class="flex items-center">
379
+ <img src="{{ editProductImage() }}" alt="{{ editProductName() }}" class="object-cover h-10 w-10 rounded-md" />
380
+ <div class="ml-3">
381
+ <p class="font-medium text-gray-900 text-sm dark:text-white">{{ editProductName() }}</p>
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
+ <div>
387
+ <label for="review-rating" class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Rating</label>
388
+ <select id="review-rating" x-model.number="editRating" class="block mt-1 w-full sm:text-sm dark:text-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:ring-blue-500 shadow-sm">
389
+ <option value="1">1 Star</option>
390
+ <option value="2">2 Stars</option>
391
+ <option value="3">3 Stars</option>
392
+ <option value="4">4 Stars</option>
393
+ <option value="5">5 Stars</option>
394
+ </select>
395
+ </div>
396
+
397
+ <div>
398
+ <label for="review-title" class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Title</label>
399
+ <input type="text" id="review-title" x-model="editTitle" required class="block mt-1 w-full sm:text-sm dark:text-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:ring-blue-500 shadow-sm" />
400
+ </div>
401
+
402
+ <div>
403
+ <label for="review-comment" class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Comment</label>
404
+ <textarea id="review-comment" x-model="editComment" rows="4" required class="block mt-1 w-full sm:text-sm dark:text-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:ring-blue-500 shadow-sm"></textarea>
405
+ </div>
406
+
407
+ <div>
408
+ <label for="review-status" class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Status</label>
409
+ <select id="review-status" x-model="editStatus" class="block mt-1 w-full sm:text-sm dark:text-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:ring-blue-500 shadow-sm">
410
+ <option value="Published">Published</option>
411
+ <option value="Under Review">Under Review</option>
412
+ <option value="Rejected">Rejected</option>
413
+ <option value="Spam">Spam</option>
414
+ </select>
415
+ </div>
416
+
417
+ <div class="flex items-center">
418
+ <input id="review-featured" type="checkbox" x-model="editFeatured" class="h-4 w-4 text-blue-600 border-gray-300 rounded dark:border-neutral-600 focus:ring-blue-500" />
419
+ <label for="review-featured" class="block ml-2 font-medium text-gray-700 text-sm dark:text-neutral-300">Featured review</label>
420
+ </div>
421
+
422
+ <div class="sm:flex sm:flex-row-reverse mt-5 sm:mt-4">
423
+ <button type="submit" class="inline-flex justify-center px-3 py-2 sm:ml-3 w-full sm:w-auto font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">Save Changes</button>
424
+ <button type="button" @click="closeReviewModal()" class="inline-flex justify-center mt-3 px-3 py-2 sm:mt-0 w-full sm:w-auto 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>
425
+ </div>
426
+ </form>
427
+ </div>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </PageLayout>
432
+ <script client>
433
+ interface Product {
434
+ id: number
435
+ name: string
436
+ imageUrl: string
437
+ category: string
438
+ }
439
+
440
+ interface Review {
441
+ id: number
442
+ productId: number
443
+ productName: string
444
+ productImage: string
445
+ customerName: string
446
+ customerEmail: string
447
+ customerAvatar: string
448
+ rating: number
449
+ title: string
450
+ comment: string
451
+ status: string
452
+ featured: boolean
453
+ dateAdded: string
454
+ helpful: number
455
+ unhelpful: number
456
+ }
457
+
458
+ const products = state<Product[]>([
459
+ { id: 1, name: 'Classic Cheeseburger', imageUrl: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Burgers' },
460
+ { id: 2, name: 'Margherita Pizza', imageUrl: 'https://images.unsplash.com/photo-1604382354936-07c5d9983bd3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Pizza' },
461
+ { id: 4, name: 'Spicy Ramen Bowl', imageUrl: 'https://images.unsplash.com/photo-1569718212165-3a8278d5f624?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Asian' },
462
+ { id: 7, name: 'Chocolate Brownie Sundae', imageUrl: 'https://images.unsplash.com/photo-1563805042-7684c019e1cb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Desserts' },
463
+ { id: 9, name: 'Chicken Wings (12 pc)', imageUrl: 'https://images.unsplash.com/photo-1608039755401-742074f0548d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Appetizers' },
464
+ { id: 12, name: 'Strawberry Cheesecake', imageUrl: 'https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', category: 'Desserts' },
465
+ ])
466
+
467
+ const reviews = state<Review[]>([
468
+ { id: 1, productId: 1, productName: 'Classic Cheeseburger', productImage: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Alex Johnson', customerEmail: 'alex.j@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/32.jpg', rating: 5, title: 'Best burger in town!', comment: 'This cheeseburger is absolutely amazing. The beef patty was juicy and flavorful, and the special sauce really makes it stand out. Will definitely order again!', status: 'Published', featured: true, dateAdded: '2023-12-15', helpful: 24, unhelpful: 2 },
469
+ { id: 2, productId: 2, productName: 'Margherita Pizza', productImage: 'https://images.unsplash.com/photo-1604382354936-07c5d9983bd3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Sarah Miller', customerEmail: 'sarah.m@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/44.jpg', rating: 4, title: 'Authentic Italian taste', comment: 'The Margherita pizza was delicious and reminded me of my trip to Naples. The crust was thin and crispy, and the fresh basil really elevated the flavor. My only complaint is that it was a bit small for the price.', status: 'Published', featured: true, dateAdded: '2023-12-10', helpful: 18, unhelpful: 3 },
470
+ { id: 3, productId: 4, productName: 'Spicy Ramen Bowl', productImage: 'https://images.unsplash.com/photo-1569718212165-3a8278d5f624?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'David Chen', customerEmail: 'd.chen@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/67.jpg', rating: 5, title: 'Authentic and spicy!', comment: 'This ramen is the real deal! The broth was rich and flavorful, and the spice level was perfect for me. The soft-boiled egg was cooked to perfection, and the noodles had just the right texture. Highly recommend!', status: 'Published', featured: false, dateAdded: '2023-12-05', helpful: 32, unhelpful: 1 },
471
+ { id: 4, productId: 7, productName: 'Chocolate Brownie Sundae', productImage: 'https://images.unsplash.com/photo-1563805042-7684c019e1cb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Emily Wilson', customerEmail: 'emily.w@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/23.jpg', rating: 5, title: 'Decadent dessert!', comment: 'This brownie sundae is to die for! The brownie was warm and gooey, and the ice cream was premium quality. The perfect way to end a meal or satisfy a sweet tooth. Worth every calorie!', status: 'Published', featured: true, dateAdded: '2023-11-28', helpful: 41, unhelpful: 0 },
472
+ { id: 5, productId: 9, productName: 'Chicken Wings (12 pc)', productImage: 'https://images.unsplash.com/photo-1608039755401-742074f0548d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Michael Thompson', customerEmail: 'm.thompson@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/42.jpg', rating: 3, title: 'Good but not great', comment: 'The wings were crispy and the portion size was generous, but I found the Buffalo sauce to be a bit too vinegary for my taste. The Honey Garlic flavor was much better. Service was quick though!', status: 'Published', featured: false, dateAdded: '2023-11-20', helpful: 8, unhelpful: 5 },
473
+ { id: 6, productId: 12, productName: 'Strawberry Cheesecake', productImage: 'https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Jessica Lee', customerEmail: 'jessica.l@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/56.jpg', rating: 4, title: 'Creamy and delicious', comment: 'This cheesecake has the perfect texture - creamy but not too heavy. The strawberry topping was made with fresh berries and not too sweet. My only suggestion would be to have a slightly thicker crust.', status: 'Published', featured: false, dateAdded: '2023-11-15', helpful: 15, unhelpful: 2 },
474
+ { id: 7, productId: 1, productName: 'Classic Cheeseburger', productImage: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Robert Garcia', customerEmail: 'r.garcia@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/78.jpg', rating: 2, title: 'Disappointed with my order', comment: 'I was really looking forward to this burger, but it arrived cold and the bun was soggy. The patty itself was tasty, but the overall experience was disappointing. Hope they can improve their delivery packaging.', status: 'Under Review', featured: false, dateAdded: '2023-12-18', helpful: 3, unhelpful: 1 },
475
+ { id: 8, productId: 4, productName: 'Spicy Ramen Bowl', productImage: 'https://images.unsplash.com/photo-1569718212165-3a8278d5f624?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Sophia Martinez', customerEmail: 's.martinez@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/90.jpg', rating: 5, title: 'Perfect comfort food!', comment: 'This ramen bowl is exactly what I needed on a cold day. The broth was rich and flavorful, and the spice level was just right - enough to warm you up without being overwhelming. The noodles had the perfect texture and the toppings were generous. Will definitely order again!', status: 'Published', featured: true, dateAdded: '2023-12-01', helpful: 27, unhelpful: 0 },
476
+ { id: 9, productId: 2, productName: 'Margherita Pizza', productImage: 'https://images.unsplash.com/photo-1604382354936-07c5d9983bd3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Daniel Brown', customerEmail: 'd.brown@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/22.jpg', rating: 1, title: 'Not what I expected', comment: 'I was very disappointed with this pizza. The crust was burnt on the bottom and the cheese was barely melted. For the price, I expected much better quality. Will not be ordering this again.', status: 'Under Review', featured: false, dateAdded: '2023-12-17', helpful: 5, unhelpful: 2 },
477
+ { id: 10, productId: 7, productName: 'Chocolate Brownie Sundae', productImage: 'https://images.unsplash.com/photo-1563805042-7684c019e1cb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Olivia Taylor', customerEmail: 'o.taylor@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/12.jpg', rating: 4, title: 'Great dessert option', comment: 'This brownie sundae was a delightful treat! The brownie was rich and chocolatey, and the ice cream complemented it perfectly. My only suggestion would be to add more chocolate sauce. Otherwise, it was perfect!', status: 'Published', featured: false, dateAdded: '2023-11-25', helpful: 19, unhelpful: 3 },
478
+ { id: 11, productId: 9, productName: 'Chicken Wings (12 pc)', productImage: 'https://images.unsplash.com/photo-1608039755401-742074f0548d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'William Clark', customerEmail: 'w.clark@example.com', customerAvatar: 'https://randomuser.me/api/portraits/men/52.jpg', rating: 5, title: 'Best wings in town!', comment: 'These wings are absolutely fantastic! I tried all three flavors and they were all delicious, but the BBQ was my favorite. The wings were meaty and cooked perfectly - crispy on the outside and juicy on the inside. Great value for the price too!', status: 'Published', featured: true, dateAdded: '2023-11-10', helpful: 38, unhelpful: 1 },
479
+ { id: 12, productId: 12, productName: 'Strawberry Cheesecake', productImage: 'https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80', customerName: 'Emma Wilson', customerEmail: 'e.wilson@example.com', customerAvatar: 'https://randomuser.me/api/portraits/women/33.jpg', rating: 3, title: 'Good but not great', comment: 'The cheesecake itself had a nice texture and wasn\'t too sweet, which I appreciated. However, the strawberry topping tasted a bit artificial to me. I would have preferred fresh strawberries. It was still enjoyable, just not exceptional.', status: 'Published', featured: false, dateAdded: '2023-12-08', helpful: 7, unhelpful: 4 },
480
+ ])
481
+
482
+ const searchQuery = state('')
483
+ const sortBy = state('dateAdded')
484
+ const sortOrder = state<'asc' | 'desc'>('desc')
485
+ const productFilter = state('all')
486
+ const ratingFilter = state('all')
487
+ const statusFilter = state('all')
488
+ const viewMode = state('detailed')
489
+
490
+ const currentPage = state(1)
491
+ const itemsPerPage = state(5)
492
+
493
+ const showReviewModal = state(false)
494
+ const editingReviewId = state<number | null>(null)
495
+ const editingReview = state<Review | null>(null)
496
+ const editTitle = state('')
497
+ const editComment = state('')
498
+ const editRating = state(5)
499
+ const editStatus = state('Published')
500
+ const editFeatured = state(false)
501
+
502
+ function productList(): Product[] { return products() }
503
+
504
+ const filteredReviews = derived(() => {
505
+ const list = reviews()
506
+ const q = searchQuery().toLowerCase()
507
+ const pf = productFilter()
508
+ const rf = ratingFilter()
509
+ const sf = statusFilter()
510
+ return list
511
+ .filter((r) => {
512
+ const matchesSearch = r.customerName.toLowerCase().includes(q)
513
+ || r.title.toLowerCase().includes(q)
514
+ || r.comment.toLowerCase().includes(q)
515
+ || r.productName.toLowerCase().includes(q)
516
+ const matchesProduct = pf === 'all' || r.productId.toString() === pf
517
+ const matchesRating = rf === 'all' || r.rating.toString() === rf
518
+ const matchesStatus = sf === 'all' || r.status === sf
519
+ return matchesSearch && matchesProduct && matchesRating && matchesStatus
520
+ })
521
+ .sort((a, b) => {
522
+ let comparison = 0
523
+ const sb = sortBy()
524
+ if (sb === 'dateAdded')
525
+ comparison = new Date(a.dateAdded).getTime() - new Date(b.dateAdded).getTime()
526
+ else if (sb === 'rating')
527
+ comparison = a.rating - b.rating
528
+ else if (sb === 'helpful')
529
+ comparison = a.helpful - b.helpful
530
+ else if (sb === 'customerName')
531
+ comparison = a.customerName.localeCompare(b.customerName)
532
+ return sortOrder() === 'asc' ? comparison : -comparison
533
+ })
534
+ })
535
+
536
+ const totalPages = derived(() => Math.max(1, Math.ceil(filteredReviews().length / itemsPerPage())))
537
+ const paginatedReviews = derived(() => {
538
+ const start = (currentPage() - 1) * itemsPerPage()
539
+ return filteredReviews().slice(start, start + itemsPerPage())
540
+ })
541
+ const pageNumbers = derived(() => Array.from({ length: totalPages() }, (_, i) => i + 1))
542
+ const hasNoReviews = derived(() => paginatedReviews().length === 0)
543
+ const hasReviews = derived(() => paginatedReviews().length > 0)
544
+ const isDetailed = derived(() => viewMode() === 'detailed')
545
+ const isCompact = derived(() => viewMode() === 'compact')
546
+ const hasMultiplePages = derived(() => totalPages() > 1)
547
+
548
+ const totalReviews = derived(() => reviews().length)
549
+ const publishedReviews = derived(() => reviews().filter(r => r.status === 'Published').length)
550
+ const pendingReviews = derived(() => reviews().filter(r => r.status === 'Under Review').length)
551
+ const averageRating = derived(() => {
552
+ const list = reviews()
553
+ if (!list.length)
554
+ return '0.0'
555
+ const total = list.reduce((sum, r) => sum + r.rating, 0)
556
+ return (total / list.length).toFixed(1)
557
+ })
558
+
559
+ const totalFilteredReviews = derived(() => filteredReviews().length)
560
+ const rangeStart = derived(() => totalFilteredReviews() === 0 ? 0 : (currentPage() - 1) * itemsPerPage() + 1)
561
+ const rangeEnd = derived(() => Math.min(currentPage() * itemsPerPage(), totalFilteredReviews()))
562
+
563
+ function editCustomerName(): string { return editingReview()?.customerName ?? '' }
564
+ function editCustomerEmail(): string { return editingReview()?.customerEmail ?? '' }
565
+ function editCustomerAvatar(): string { return editingReview()?.customerAvatar ?? '' }
566
+ function editProductName(): string { return editingReview()?.productName ?? '' }
567
+ function editProductImage(): string { return editingReview()?.productImage ?? '' }
568
+
569
+ function setViewMode(mode: string): void {
570
+ viewMode.set(mode)
571
+ }
572
+
573
+ function toggleSort(column: string): void {
574
+ if (sortBy() === column)
575
+ sortOrder.set(sortOrder() === 'asc' ? 'desc' : 'asc')
576
+ else {
577
+ sortBy.set(column)
578
+ sortOrder.set('desc')
579
+ }
580
+ }
581
+
582
+ function changePage(page: number): void {
583
+ currentPage.set(page)
584
+ }
585
+
586
+ function previousPage(): void {
587
+ if (currentPage() > 1)
588
+ currentPage.update(p => p - 1)
589
+ }
590
+
591
+ function nextPage(): void {
592
+ if (currentPage() < totalPages())
593
+ currentPage.update(p => p + 1)
594
+ }
595
+
596
+ function getStatusClass(status: string): string {
597
+ switch (status) {
598
+ case 'Published': return 'bg-green-50 text-green-700 ring-1 ring-inset ring-green-600/20 dark:bg-green-900/30 dark:text-green-400'
599
+ case 'Under Review': return 'bg-yellow-50 text-yellow-700 ring-1 ring-inset ring-yellow-600/20 dark:bg-yellow-900/30 dark:text-yellow-400'
600
+ case 'Rejected': return 'bg-red-50 text-red-700 ring-1 ring-inset ring-red-600/20 dark:bg-red-900/30 dark:text-red-400'
601
+ default: return 'bg-gray-50 text-gray-700 ring-1 ring-inset ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400'
602
+ }
603
+ }
604
+
605
+ function getRatingStars(rating: number): string {
606
+ return '★'.repeat(rating) + '☆'.repeat(5 - rating)
607
+ }
608
+
609
+ function findReview(id: number): Review | undefined {
610
+ return reviews().find(r => r.id === id)
611
+ }
612
+
613
+ function openEditReviewModal(id: number): void {
614
+ const target = findReview(id)
615
+ if (!target)
616
+ return
617
+ editingReviewId.set(id)
618
+ editingReview.set({ ...target })
619
+ editTitle.set(target.title)
620
+ editComment.set(target.comment)
621
+ editRating.set(target.rating)
622
+ editStatus.set(target.status)
623
+ editFeatured.set(target.featured)
624
+ showReviewModal.set(true)
625
+ }
626
+
627
+ function closeReviewModal(): void {
628
+ showReviewModal.set(false)
629
+ editingReviewId.set(null)
630
+ editingReview.set(null)
631
+ }
632
+
633
+ function saveReview(): void {
634
+ const id = editingReviewId()
635
+ if (id == null)
636
+ return
637
+ reviews.update(list => list.map(r => r.id === id
638
+ ? { ...r, title: editTitle(), comment: editComment(), rating: editRating(), status: editStatus(), featured: editFeatured() }
639
+ : r))
640
+ closeReviewModal()
641
+ }
642
+
643
+ function toggleFeature(id: number): void {
644
+ reviews.update(list => list.map(r => r.id === id ? { ...r, featured: !r.featured } : r))
645
+ }
646
+
647
+ function updateReviewStatus(id: number, newStatus: string): void {
648
+ reviews.update(list => list.map(r => r.id === id ? { ...r, status: newStatus } : r))
649
+ }
650
+
651
+ function deleteReview(id: number): void {
652
+ reviews.update(list => list.filter(r => r.id !== id))
653
+ }
654
+ </script>
655
+
656
+ @endsection