@nexttylabs/echo 0.2.0

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 (579) hide show
  1. package/.changeset/README.md +21 -0
  2. package/.changeset/config.json +11 -0
  3. package/.changeset/cozy-ghosts-care.md +5 -0
  4. package/.changeset/sharp-lines-stand.md +5 -0
  5. package/.changeset/sour-doodles-eat.md +5 -0
  6. package/.changeset/tender-moose-shop.md +5 -0
  7. package/.github/pull_request_template.md +13 -0
  8. package/.github/workflows/ci.yml +41 -0
  9. package/.github/workflows/publish.yml +44 -0
  10. package/.github/workflows/release.yml +73 -0
  11. package/AGENTS.md +92 -0
  12. package/CHANGELOG.md +13 -0
  13. package/Dockerfile +57 -0
  14. package/LICENSE +661 -0
  15. package/Makefile +77 -0
  16. package/README.md +198 -0
  17. package/app/(auth)/login/page.tsx +53 -0
  18. package/app/(auth)/register/page.tsx +48 -0
  19. package/app/(auth)/sign-in/page.tsx +22 -0
  20. package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +103 -0
  21. package/app/(dashboard)/admin/feedback/[id]/page.tsx +154 -0
  22. package/app/(dashboard)/admin/feedback/new/page.tsx +91 -0
  23. package/app/(dashboard)/admin/feedback/page.tsx +81 -0
  24. package/app/(dashboard)/admin/layout.tsx +48 -0
  25. package/app/(dashboard)/analytics/portal/page.tsx +30 -0
  26. package/app/(dashboard)/dashboard/page.tsx +133 -0
  27. package/app/(dashboard)/layout.tsx +69 -0
  28. package/app/(dashboard)/no-access/page.tsx +45 -0
  29. package/app/(dashboard)/settings/access/page.tsx +56 -0
  30. package/app/(dashboard)/settings/api-keys/page.tsx +55 -0
  31. package/app/(dashboard)/settings/appearance/page.tsx +40 -0
  32. package/app/(dashboard)/settings/branding/page.tsx +62 -0
  33. package/app/(dashboard)/settings/changelog/page.tsx +51 -0
  34. package/app/(dashboard)/settings/danger-zone/page.tsx +92 -0
  35. package/app/(dashboard)/settings/feedback/page.tsx +63 -0
  36. package/app/(dashboard)/settings/integrations/page.tsx +94 -0
  37. package/app/(dashboard)/settings/layout.tsx +43 -0
  38. package/app/(dashboard)/settings/modules/page.tsx +54 -0
  39. package/app/(dashboard)/settings/notifications/page.tsx +48 -0
  40. package/app/(dashboard)/settings/organization/page.tsx +104 -0
  41. package/app/(dashboard)/settings/organization/portal/access/page.tsx +22 -0
  42. package/app/(dashboard)/settings/organization/portal/experience/page.tsx +22 -0
  43. package/app/(dashboard)/settings/organization/portal/growth/page.tsx +22 -0
  44. package/app/(dashboard)/settings/organization/portal/layout.tsx +24 -0
  45. package/app/(dashboard)/settings/organization/portal/page.tsx +22 -0
  46. package/app/(dashboard)/settings/organizations/[orgId]/members/page.tsx +69 -0
  47. package/app/(dashboard)/settings/organizations/new/page.tsx +36 -0
  48. package/app/(dashboard)/settings/page.tsx +22 -0
  49. package/app/(dashboard)/settings/portal-access/page.tsx +53 -0
  50. package/app/(dashboard)/settings/portal-branding/page.tsx +59 -0
  51. package/app/(dashboard)/settings/portal-growth/page.tsx +57 -0
  52. package/app/(dashboard)/settings/portal-modules/page.tsx +49 -0
  53. package/app/(dashboard)/settings/portal-resources/page.tsx +66 -0
  54. package/app/(dashboard)/settings/profile/page.tsx +48 -0
  55. package/app/(dashboard)/settings/widgets/page.tsx +63 -0
  56. package/app/(public)/[organizationSlug]/changelog/page.tsx +109 -0
  57. package/app/(public)/[organizationSlug]/feedback/[id]/page.tsx +146 -0
  58. package/app/(public)/[organizationSlug]/page.tsx +160 -0
  59. package/app/(public)/[organizationSlug]/roadmap/page.tsx +142 -0
  60. package/app/(public)/docs/page.tsx +48 -0
  61. package/app/(public)/feedback/[id]/not-found.tsx +33 -0
  62. package/app/(public)/feedback/[id]/page.tsx +102 -0
  63. package/app/(public)/invite/[token]/page.tsx +121 -0
  64. package/app/(public)/page.tsx +22 -0
  65. package/app/(public)/widget/[organizationId]/page.tsx +122 -0
  66. package/app/api/_utils.ts +29 -0
  67. package/app/api/admin/backup/route.ts +72 -0
  68. package/app/api/api-keys/[keyId]/route.ts +92 -0
  69. package/app/api/api-keys/route.ts +116 -0
  70. package/app/api/auth/[...all]/route.ts +21 -0
  71. package/app/api/auth/clear-session/route.ts +43 -0
  72. package/app/api/auth/register/handler.ts +176 -0
  73. package/app/api/auth/register/route.ts +26 -0
  74. package/app/api/docs/route.ts +28 -0
  75. package/app/api/feedback/[id]/comments/[commentId]/route.ts +105 -0
  76. package/app/api/feedback/[id]/comments/route.ts +421 -0
  77. package/app/api/feedback/[id]/duplicates/route.ts +285 -0
  78. package/app/api/feedback/[id]/handler.ts +91 -0
  79. package/app/api/feedback/[id]/processing-status/route.ts +199 -0
  80. package/app/api/feedback/[id]/reclassify/route.ts +145 -0
  81. package/app/api/feedback/[id]/route.ts +511 -0
  82. package/app/api/feedback/[id]/suggest-tags/route.ts +227 -0
  83. package/app/api/feedback/[id]/sync-github/route.ts +52 -0
  84. package/app/api/feedback/[id]/vote/route.ts +431 -0
  85. package/app/api/feedback/bulk/route.ts +212 -0
  86. package/app/api/feedback/handler.ts +138 -0
  87. package/app/api/feedback/route.ts +298 -0
  88. package/app/api/feedback/similar/route.ts +100 -0
  89. package/app/api/health/route.test.ts +64 -0
  90. package/app/api/health/route.ts +92 -0
  91. package/app/api/identify/jwt/route.ts +29 -0
  92. package/app/api/integrations/github/route.ts +196 -0
  93. package/app/api/internal/domain-lookup/route.ts +67 -0
  94. package/app/api/invitations/accept/handler.ts +101 -0
  95. package/app/api/invitations/accept/route.ts +29 -0
  96. package/app/api/notifications/preferences/route.ts +109 -0
  97. package/app/api/organizations/[orgId]/handler.ts +123 -0
  98. package/app/api/organizations/[orgId]/invitations/handler.ts +121 -0
  99. package/app/api/organizations/[orgId]/invitations/route.ts +29 -0
  100. package/app/api/organizations/[orgId]/members/[memberId]/handler.ts +208 -0
  101. package/app/api/organizations/[orgId]/members/[memberId]/route.ts +30 -0
  102. package/app/api/organizations/[orgId]/members/handler.ts +77 -0
  103. package/app/api/organizations/[orgId]/members/route.ts +29 -0
  104. package/app/api/organizations/[orgId]/route.ts +30 -0
  105. package/app/api/organizations/handler.ts +97 -0
  106. package/app/api/organizations/route.ts +29 -0
  107. package/app/api/tags/sync/route.ts +88 -0
  108. package/app/api/upload/handler.ts +79 -0
  109. package/app/api/upload/route.ts +37 -0
  110. package/app/api/v1/feedback/[id]/route.ts +276 -0
  111. package/app/api/v1/feedback/route.ts +250 -0
  112. package/app/api/v1/spec/route.ts +356 -0
  113. package/app/api/webhooks/[webhookId]/route.ts +213 -0
  114. package/app/api/webhooks/github/route.ts +158 -0
  115. package/app/api/webhooks/route.ts +143 -0
  116. package/app/favicon.ico +0 -0
  117. package/app/globals.css +139 -0
  118. package/app/health/route.ts +108 -0
  119. package/app/layout.tsx +60 -0
  120. package/bun.lock +2503 -0
  121. package/components/api/rate-limit-info.tsx +86 -0
  122. package/components/api-keys/api-key-manager.tsx +262 -0
  123. package/components/auth/login-form.tsx +207 -0
  124. package/components/auth/register-form.tsx +230 -0
  125. package/components/comment/comment-form.tsx +111 -0
  126. package/components/comment/internal-notes.tsx +219 -0
  127. package/components/comment/public-comments.tsx +387 -0
  128. package/components/component-example-client-only.tsx +29 -0
  129. package/components/component-example.tsx +519 -0
  130. package/components/dashboard/index.ts +22 -0
  131. package/components/dashboard/organization-switcher.tsx +96 -0
  132. package/components/dashboard/quick-actions.tsx +57 -0
  133. package/components/dashboard/recent-feedback-list.tsx +152 -0
  134. package/components/dashboard/stats-cards.tsx +88 -0
  135. package/components/dashboard/status-chart.tsx +106 -0
  136. package/components/example.tsx +70 -0
  137. package/components/feedback/attachment-list.tsx +103 -0
  138. package/components/feedback/auto-classification-badge.tsx +92 -0
  139. package/components/feedback/classification-override.tsx +64 -0
  140. package/components/feedback/duplicate-suggestions-inline.tsx +158 -0
  141. package/components/feedback/duplicate-suggestions.tsx +188 -0
  142. package/components/feedback/embedded-feedback-form.tsx +439 -0
  143. package/components/feedback/feedback-actions.tsx +160 -0
  144. package/components/feedback/feedback-bulk-actions.tsx +184 -0
  145. package/components/feedback/feedback-detail-view.tsx +321 -0
  146. package/components/feedback/feedback-detail.tsx +305 -0
  147. package/components/feedback/feedback-edit-form.tsx +131 -0
  148. package/components/feedback/feedback-filters.tsx +222 -0
  149. package/components/feedback/feedback-list-controls.tsx +433 -0
  150. package/components/feedback/feedback-list-item.tsx +298 -0
  151. package/components/feedback/feedback-list-skeleton.tsx +49 -0
  152. package/components/feedback/feedback-list.tsx +523 -0
  153. package/components/feedback/feedback-sorter.tsx +117 -0
  154. package/components/feedback/feedback-stats.tsx +124 -0
  155. package/components/feedback/file-upload.tsx +289 -0
  156. package/components/feedback/processing-status.tsx +161 -0
  157. package/components/feedback/status-history.tsx +134 -0
  158. package/components/feedback/status-selector.tsx +153 -0
  159. package/components/feedback/submit-on-behalf-form.tsx +403 -0
  160. package/components/feedback/tag-suggestions.tsx +212 -0
  161. package/components/feedback/vote-button.tsx +113 -0
  162. package/components/feedback/vote-list.tsx +108 -0
  163. package/components/integrations/github-config.tsx +200 -0
  164. package/components/landing/hero.tsx +150 -0
  165. package/components/layout/dashboard-layout.tsx +59 -0
  166. package/components/layout/index.ts +20 -0
  167. package/components/layout/language-switcher.tsx +129 -0
  168. package/components/layout/mobile-sidebar.tsx +66 -0
  169. package/components/layout/sidebar.tsx +279 -0
  170. package/components/portal/changelog-entry.tsx +132 -0
  171. package/components/portal/changelog-list.tsx +85 -0
  172. package/components/portal/contributor-badge.tsx +29 -0
  173. package/components/portal/contributors-sidebar.tsx +98 -0
  174. package/components/portal/create-post-dialog.tsx +247 -0
  175. package/components/portal/feedback-board.tsx +205 -0
  176. package/components/portal/feedback-post-card.tsx +198 -0
  177. package/components/portal/help-center.tsx +169 -0
  178. package/components/portal/leaderboard.tsx +29 -0
  179. package/components/portal/portal-header.tsx +153 -0
  180. package/components/portal/portal-layout.tsx +62 -0
  181. package/components/portal/portal-modules-panel.tsx +118 -0
  182. package/components/portal/portal-nav.tsx +59 -0
  183. package/components/portal/portal-overview.tsx +174 -0
  184. package/components/portal/portal-settings-nav.tsx +62 -0
  185. package/components/portal/portal-settings-shell.tsx +71 -0
  186. package/components/portal/portal-shell.tsx +62 -0
  187. package/components/portal/portal-tab-nav.tsx +77 -0
  188. package/components/portal/project-switcher.tsx +20 -0
  189. package/components/portal/roadmap-board.tsx +82 -0
  190. package/components/portal/roadmap-card.tsx +76 -0
  191. package/components/portal/roadmap-column.tsx +78 -0
  192. package/components/portal/settings-forms/access-form.tsx +194 -0
  193. package/components/portal/settings-forms/copy-form.tsx +95 -0
  194. package/components/portal/settings-forms/index.ts +23 -0
  195. package/components/portal/settings-forms/languages-form.tsx +223 -0
  196. package/components/portal/settings-forms/seo-form.tsx +156 -0
  197. package/components/portal/settings-forms/sharing-form.tsx +155 -0
  198. package/components/portal/settings-forms/theme-form.tsx +104 -0
  199. package/components/settings/api-keys-list.tsx +167 -0
  200. package/components/settings/appearance-form.tsx +71 -0
  201. package/components/settings/index.ts +25 -0
  202. package/components/settings/invite-member-form.tsx +119 -0
  203. package/components/settings/notification-preferences.tsx +174 -0
  204. package/components/settings/organization-form.tsx +165 -0
  205. package/components/settings/organization-members-list.tsx +197 -0
  206. package/components/settings/profile-form.tsx +124 -0
  207. package/components/settings/role-selector.tsx +57 -0
  208. package/components/settings/settings-sidebar.tsx +115 -0
  209. package/components/shared/pagination.tsx +215 -0
  210. package/components/ui/alert-dialog.tsx +201 -0
  211. package/components/ui/alert.tsx +75 -0
  212. package/components/ui/avatar.tsx +126 -0
  213. package/components/ui/badge.tsx +62 -0
  214. package/components/ui/button.tsx +77 -0
  215. package/components/ui/card.tsx +111 -0
  216. package/components/ui/combobox.tsx +311 -0
  217. package/components/ui/dialog.tsx +158 -0
  218. package/components/ui/dropdown-menu.tsx +272 -0
  219. package/components/ui/field.tsx +256 -0
  220. package/components/ui/input-group.tsx +164 -0
  221. package/components/ui/input.tsx +36 -0
  222. package/components/ui/label.tsx +41 -0
  223. package/components/ui/pagination.tsx +142 -0
  224. package/components/ui/select.tsx +202 -0
  225. package/components/ui/separator.tsx +45 -0
  226. package/components/ui/sheet.tsx +151 -0
  227. package/components/ui/skeleton.tsx +32 -0
  228. package/components/ui/switch.tsx +49 -0
  229. package/components/ui/table.tsx +118 -0
  230. package/components/ui/tabs.tsx +107 -0
  231. package/components/ui/textarea.tsx +35 -0
  232. package/components/ui/tooltip.tsx +78 -0
  233. package/components/widget/widget-form.tsx +439 -0
  234. package/components.json +24 -0
  235. package/db/init/01-init.sql +13 -0
  236. package/docker-compose.dev.yml +26 -0
  237. package/docker-compose.yml +98 -0
  238. package/docs/architecture.md +259 -0
  239. package/docs/component-inventory.md +261 -0
  240. package/docs/database-migrations.md +76 -0
  241. package/docs/development-guide.md +209 -0
  242. package/docs/e2e-user-flows.csv +31 -0
  243. package/docs/er-diagram-feedback.mmd +138 -0
  244. package/docs/er-diagram.mmd +281 -0
  245. package/docs/i18n-check-report.md +296 -0
  246. package/docs/index.md +214 -0
  247. package/docs/logic-chain.md +94 -0
  248. package/docs/plans/2026-01-02-database-migration-scripts.md +496 -0
  249. package/docs/plans/2026-01-02-user-login-design.md +37 -0
  250. package/docs/plans/2026-01-02-user-login.md +437 -0
  251. package/docs/plans/2026-01-02-user-registration-design.md +47 -0
  252. package/docs/plans/2026-01-02-user-registration.md +628 -0
  253. package/docs/plans/2026-01-03-roles-permissions-design.md +20 -0
  254. package/docs/plans/2026-01-03-roles-permissions.md +266 -0
  255. package/docs/plans/2026-01-05-authentication-middleware.md +207 -0
  256. package/docs/plans/2026-01-05-member-removal.md +186 -0
  257. package/docs/plans/2026-01-05-organization-creation.md +374 -0
  258. package/docs/plans/2026-01-05-rbac-middleware.md +112 -0
  259. package/docs/plans/2026-01-05-role-configuration.md +441 -0
  260. package/docs/plans/2026-01-06-file-upload-support.md +804 -0
  261. package/docs/plans/2026-01-06-permission-check-hook.md +155 -0
  262. package/docs/plans/2026-01-06-resource-ownership-check.md +231 -0
  263. package/docs/plans/2026-01-07-feedback-tracking-link.md +459 -0
  264. package/docs/plans/2026-01-09-logout-redirect-design.md +52 -0
  265. package/docs/plans/2026-01-09-phase2-3-plan.md +654 -0
  266. package/docs/plans/2026-01-09-portal-execution-plan.md +408 -0
  267. package/docs/plans/2026-01-09-project-delete-feature-design.md +163 -0
  268. package/docs/plans/2026-01-09-project-delete-implementation.md +451 -0
  269. package/docs/plans/2026-01-09-project-edit-delete-design.md +52 -0
  270. package/docs/plans/2026-01-09-settings-center-design.md +114 -0
  271. package/docs/plans/2026-01-09-settings-center.md +948 -0
  272. package/docs/plans/2026-01-10-organization-only-design.md +66 -0
  273. package/docs/plans/2026-01-10-organization-only-implementation.md +433 -0
  274. package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +18 -0
  275. package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +296 -0
  276. package/docs/plans/2026-01-14-e2e-playwright-feedback.md +173 -0
  277. package/docs/plans/2026-01-15-feedback-management-org-context-design.md +82 -0
  278. package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +521 -0
  279. package/docs/plans/2026-01-16-admin-feedback-filters-design.md +75 -0
  280. package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +293 -0
  281. package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +180 -0
  282. package/docs/plans/2026-01-16-e2e-test-fixes.md +158 -0
  283. package/docs/plans/2026-01-17-admin-feedback-filters.md +214 -0
  284. package/docs/plans/2026-01-17-admin-feedback-improvements.md +453 -0
  285. package/docs/plans/2026-01-18-changesets-design.md +40 -0
  286. package/docs/product_changes.md +37 -0
  287. package/docs/project-overview.md +159 -0
  288. package/docs/project-scan-report.json +104 -0
  289. package/docs/route-role-visibility.md +51 -0
  290. package/docs/source-tree-analysis.md +150 -0
  291. package/docs/testing/delete-project-manual-tests.md +18 -0
  292. package/docs/user-story-tracking.md +191 -0
  293. package/drizzle.config.ts +32 -0
  294. package/eslint.config.mjs +19 -0
  295. package/hooks/use-permissions.ts +56 -0
  296. package/i18n/config.ts +45 -0
  297. package/i18n/request.ts +28 -0
  298. package/i18n/resolve-locale.ts +38 -0
  299. package/lib/api/errors.ts +62 -0
  300. package/lib/auth/cli-config.ts +35 -0
  301. package/lib/auth/client.ts +20 -0
  302. package/lib/auth/config.ts +55 -0
  303. package/lib/auth/jwt-identity.ts +21 -0
  304. package/lib/auth/org-context.ts +71 -0
  305. package/lib/auth/organization.ts +107 -0
  306. package/lib/auth/permissions.ts +87 -0
  307. package/lib/auth/session.ts +23 -0
  308. package/lib/config/rate-limits.ts +64 -0
  309. package/lib/dashboard/get-dashboard-stats.ts +136 -0
  310. package/lib/db/index.ts +41 -0
  311. package/lib/db/migrate.test.ts +49 -0
  312. package/lib/db/migrate.ts +62 -0
  313. package/lib/db/migrations/.gitkeep +0 -0
  314. package/lib/db/migrations/0000_cynical_gladiator.sql +53 -0
  315. package/lib/db/migrations/0001_wandering_sunfire.sql +27 -0
  316. package/lib/db/migrations/0002_shallow_speedball.sql +1 -0
  317. package/lib/db/migrations/0003_add_org_description.sql +1 -0
  318. package/lib/db/migrations/0003_boring_wild_pack.sql +13 -0
  319. package/lib/db/migrations/0004_windy_tyrannus.sql +27 -0
  320. package/lib/db/migrations/0005_perpetual_doorman.sql +5 -0
  321. package/lib/db/migrations/0006_aberrant_captain_midlands.sql +13 -0
  322. package/lib/db/migrations/0007_clever_captain_cross.sql +14 -0
  323. package/lib/db/migrations/0008_sparkling_pandemic.sql +2 -0
  324. package/lib/db/migrations/0009_happy_black_tom.sql +29 -0
  325. package/lib/db/migrations/0010_kind_junta.sql +8 -0
  326. package/lib/db/migrations/0011_mute_squadron_supreme.sql +25 -0
  327. package/lib/db/migrations/0012_giant_power_man.sql +24 -0
  328. package/lib/db/migrations/0013_damp_titanium_man.sql +17 -0
  329. package/lib/db/migrations/0014_blue_alice.sql +18 -0
  330. package/lib/db/migrations/0015_webhook_tables.sql +41 -0
  331. package/lib/db/migrations/0016_github_integration.sql +30 -0
  332. package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +22 -0
  333. package/lib/db/migrations/0017_slimy_inhumans.sql +6 -0
  334. package/lib/db/migrations/0018_same_spitfire.sql +1 -0
  335. package/lib/db/migrations/0019_jittery_loners.sql +16 -0
  336. package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +14 -0
  337. package/lib/db/migrations/meta/0000_snapshot.json +374 -0
  338. package/lib/db/migrations/meta/0001_snapshot.json +553 -0
  339. package/lib/db/migrations/meta/0002_snapshot.json +560 -0
  340. package/lib/db/migrations/meta/0003_snapshot.json +650 -0
  341. package/lib/db/migrations/meta/0004_snapshot.json +852 -0
  342. package/lib/db/migrations/meta/0005_snapshot.json +900 -0
  343. package/lib/db/migrations/meta/0006_snapshot.json +1011 -0
  344. package/lib/db/migrations/meta/0007_snapshot.json +1125 -0
  345. package/lib/db/migrations/meta/0008_snapshot.json +1146 -0
  346. package/lib/db/migrations/meta/0009_snapshot.json +1386 -0
  347. package/lib/db/migrations/meta/0010_snapshot.json +1419 -0
  348. package/lib/db/migrations/meta/0011_snapshot.json +1615 -0
  349. package/lib/db/migrations/meta/0012_snapshot.json +1805 -0
  350. package/lib/db/migrations/meta/0013_snapshot.json +1948 -0
  351. package/lib/db/migrations/meta/0014_snapshot.json +2082 -0
  352. package/lib/db/migrations/meta/0015_snapshot.json +2476 -0
  353. package/lib/db/migrations/meta/0016_snapshot.json +2633 -0
  354. package/lib/db/migrations/meta/0017_snapshot.json +2680 -0
  355. package/lib/db/migrations/meta/0018_snapshot.json +2686 -0
  356. package/lib/db/migrations/meta/0019_snapshot.json +2741 -0
  357. package/lib/db/migrations/meta/_journal.json +146 -0
  358. package/lib/db/schema/ai-processing.ts +90 -0
  359. package/lib/db/schema/api-keys.ts +61 -0
  360. package/lib/db/schema/attachments.ts +48 -0
  361. package/lib/db/schema/auth.ts +111 -0
  362. package/lib/db/schema/comments.ts +74 -0
  363. package/lib/db/schema/duplicates.ts +80 -0
  364. package/lib/db/schema/feedback.ts +88 -0
  365. package/lib/db/schema/github-integrations.ts +66 -0
  366. package/lib/db/schema/index.ts +35 -0
  367. package/lib/db/schema/invitations.ts +32 -0
  368. package/lib/db/schema/notifications.ts +85 -0
  369. package/lib/db/schema/organization-members.ts +37 -0
  370. package/lib/db/schema/organization-settings.ts +134 -0
  371. package/lib/db/schema/organizations.ts +30 -0
  372. package/lib/db/schema/projects.ts +145 -0
  373. package/lib/db/schema/status-history.ts +63 -0
  374. package/lib/db/schema/tags.ts +194 -0
  375. package/lib/db/schema/user-profiles.ts +31 -0
  376. package/lib/db/schema/votes.ts +60 -0
  377. package/lib/db/schema/webhooks.ts +106 -0
  378. package/lib/feedback/filters.ts +28 -0
  379. package/lib/feedback/find-similar.ts +49 -0
  380. package/lib/feedback/get-feedback-by-id.ts +159 -0
  381. package/lib/feedback/prefill.ts +51 -0
  382. package/lib/http/get-request-url.ts +28 -0
  383. package/lib/integrations/github.ts +159 -0
  384. package/lib/invitations.ts +22 -0
  385. package/lib/logger.test.ts +31 -0
  386. package/lib/logger.ts +58 -0
  387. package/lib/middleware/api-key.ts +126 -0
  388. package/lib/middleware/rate-limit-keys.ts +47 -0
  389. package/lib/middleware/rate-limit.ts +148 -0
  390. package/lib/middleware/rbac.ts +39 -0
  391. package/lib/middleware/request-id.test.ts +28 -0
  392. package/lib/middleware/request-id.ts +30 -0
  393. package/lib/middleware/request-logger.test.ts +36 -0
  394. package/lib/middleware/request-logger.ts +41 -0
  395. package/lib/middleware/with-rate-limit.ts +33 -0
  396. package/lib/portal/analytics.ts +20 -0
  397. package/lib/portal/contributors.ts +27 -0
  398. package/lib/portal/i18n.ts +20 -0
  399. package/lib/portal/leaderboard-settings.ts +20 -0
  400. package/lib/portal/modules.ts +20 -0
  401. package/lib/portal/portal-copy.ts +20 -0
  402. package/lib/portal/public-context.tsx +110 -0
  403. package/lib/portal/seo.ts +20 -0
  404. package/lib/portal/settings-context.ts +56 -0
  405. package/lib/portal/sharing.ts +20 -0
  406. package/lib/portal/sorting.ts +20 -0
  407. package/lib/portal/theme.ts +20 -0
  408. package/lib/services/ai/classifier.ts +296 -0
  409. package/lib/services/ai/duplicate-detector.ts +255 -0
  410. package/lib/services/ai/tag-suggester.ts +108 -0
  411. package/lib/services/api-keys.ts +164 -0
  412. package/lib/services/backup.ts +173 -0
  413. package/lib/services/email/templates.ts +158 -0
  414. package/lib/services/email.ts +68 -0
  415. package/lib/services/github-sync.ts +205 -0
  416. package/lib/services/notifications/index.ts +224 -0
  417. package/lib/services/portal-settings.ts +157 -0
  418. package/lib/swagger/config.ts +296 -0
  419. package/lib/swagger/generate.ts +400 -0
  420. package/lib/upload/file-validator.ts +52 -0
  421. package/lib/upload/storage.ts +59 -0
  422. package/lib/utils/format.ts +26 -0
  423. package/lib/utils/slug.ts +28 -0
  424. package/lib/utils.ts +23 -0
  425. package/lib/validations/auth.ts +56 -0
  426. package/lib/validations/comment.ts +44 -0
  427. package/lib/validations/feedback.ts +51 -0
  428. package/lib/validations/invitations.ts +23 -0
  429. package/lib/validations/organizations.ts +34 -0
  430. package/lib/validations/projects.ts +49 -0
  431. package/lib/validators/feedback.ts +57 -0
  432. package/lib/validators/index.ts +18 -0
  433. package/lib/webhooks/events.ts +73 -0
  434. package/lib/webhooks/index.ts +21 -0
  435. package/lib/webhooks/retry.ts +188 -0
  436. package/lib/webhooks/sender.ts +183 -0
  437. package/lib/webhooks/verify.ts +37 -0
  438. package/lib/workers/feedback-processor.ts +255 -0
  439. package/messages/en.json +965 -0
  440. package/messages/jp.json +862 -0
  441. package/messages/zh-CN.json +855 -0
  442. package/next-env.d.ts +6 -0
  443. package/next.config.ts +66 -0
  444. package/package.json +84 -0
  445. package/playwright.config.ts +44 -0
  446. package/postcss.config.mjs +7 -0
  447. package/proxy.test.ts +131 -0
  448. package/proxy.ts +190 -0
  449. package/public/file.svg +1 -0
  450. package/public/globe.svg +1 -0
  451. package/public/logo-64.svg +5 -0
  452. package/public/logo.svg +5 -0
  453. package/public/next.svg +1 -0
  454. package/public/openapi.json +673 -0
  455. package/public/uploads/.gitkeep +0 -0
  456. package/public/uploads/02695701-ded0-4c81-8a21-9326c1d65448.pdf +1 -0
  457. package/public/uploads/178843ea-2780-48ef-8988-f4cba442e4cb.pdf +1 -0
  458. package/public/uploads/24b0a9ef-da93-49da-934f-637f89c7871d.pdf +1 -0
  459. package/public/uploads/7a11626d-a8e4-4b91-a8eb-20b6213b0a5a.pdf +1 -0
  460. package/public/uploads/b0703f4d-6e7b-4aab-8191-1a7b15f1b8ee.pdf +1 -0
  461. package/public/uploads/c8de0aed-4d3a-44aa-83bb-6594b7a2ddb3.pdf +1 -0
  462. package/public/uploads/e4cce295-0d85-4525-a1b0-a61c45722e26.pdf +1 -0
  463. package/public/uploads/eb4df45e-563c-48b8-9c68-c18212312426.pdf +1 -0
  464. package/public/vercel.svg +1 -0
  465. package/public/widget/embed.js +249 -0
  466. package/public/window.svg +1 -0
  467. package/scripts/backup-db.sh +57 -0
  468. package/scripts/backup-db.ts +24 -0
  469. package/scripts/generate-openapi.ts +22 -0
  470. package/scripts/migration-helper.ts +39 -0
  471. package/scripts/pre-deploy.ts +75 -0
  472. package/scripts/restore-db.sh +60 -0
  473. package/scripts/rollback.ts +72 -0
  474. package/scripts/seed-tags.ts +48 -0
  475. package/tests/api/feedback-bulk.test.ts +47 -0
  476. package/tests/api/feedback-by-id.test.ts +67 -0
  477. package/tests/api/feedback-comments-route-import.test.ts +26 -0
  478. package/tests/api/feedback-create.test.ts +71 -0
  479. package/tests/api/feedback-delete.test.ts +160 -0
  480. package/tests/api/feedback-filter.test.ts +250 -0
  481. package/tests/api/feedback-list.test.ts +234 -0
  482. package/tests/api/feedback-route-assignee-condition.test.ts +32 -0
  483. package/tests/api/feedback-similar.test.ts +46 -0
  484. package/tests/api/feedback-sort.test.ts +261 -0
  485. package/tests/api/feedback-status-enum.test.ts +49 -0
  486. package/tests/api/feedback-status-filter.test.ts +117 -0
  487. package/tests/api/feedback-submit-on-behalf.test.ts +269 -0
  488. package/tests/api/feedback.test.ts +175 -0
  489. package/tests/api/identify-jwt.test.ts +25 -0
  490. package/tests/api/invitation-accept.test.ts +213 -0
  491. package/tests/api/organization-invitations.test.ts +186 -0
  492. package/tests/api/organization-members-list.test.ts +79 -0
  493. package/tests/api/organization-members.test.ts +340 -0
  494. package/tests/api/organizations.test.ts +149 -0
  495. package/tests/api/register.test.ts +112 -0
  496. package/tests/api/upload.test.ts +103 -0
  497. package/tests/api/vote.test.ts +82 -0
  498. package/tests/app/admin-feedback-detail-page.test.tsx +25 -0
  499. package/tests/app/admin-feedback-list-page.test.tsx +25 -0
  500. package/tests/app/admin-feedback-new-page.test.tsx +25 -0
  501. package/tests/app/health-route-helpers.test.ts +27 -0
  502. package/tests/app/login-page.test.ts +26 -0
  503. package/tests/app/portal-page.test.ts +29 -0
  504. package/tests/app/project-portal-overview.test.tsx +25 -0
  505. package/tests/app/widget-page-import.test.ts +25 -0
  506. package/tests/components/create-post-dialog-defaults.test.ts +43 -0
  507. package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +27 -0
  508. package/tests/components/feedback/embedded-feedback-form.test.tsx +96 -0
  509. package/tests/components/feedback/feedback-detail.test.tsx +25 -0
  510. package/tests/components/feedback/feedback-stats.test.tsx +49 -0
  511. package/tests/components/feedback-bulk-actions.test.tsx +39 -0
  512. package/tests/components/feedback-i18n-keys.test.ts +70 -0
  513. package/tests/components/feedback-list-controls-compile.test.ts +25 -0
  514. package/tests/components/feedback-list-controls.test.tsx +204 -0
  515. package/tests/components/feedback-list-item.test.tsx +67 -0
  516. package/tests/components/landing/hero.test.tsx +46 -0
  517. package/tests/components/layout/language-switcher.test.tsx +25 -0
  518. package/tests/components/layout/sidebar.test.tsx +157 -0
  519. package/tests/components/login-form.test.ts +25 -0
  520. package/tests/components/organization-form.test.ts +32 -0
  521. package/tests/components/organization-switcher.test.ts +25 -0
  522. package/tests/components/pagination.test.tsx +43 -0
  523. package/tests/components/portal-overview.test.tsx +25 -0
  524. package/tests/components/profile-form.test.tsx +139 -0
  525. package/tests/components/role-selector.test.ts +31 -0
  526. package/tests/components/status-chart.test.tsx +90 -0
  527. package/tests/e2e/auth.e2e.ts +323 -0
  528. package/tests/e2e/feedback-actions.e2e.ts +471 -0
  529. package/tests/e2e/feedback-attachment.e2e.ts +168 -0
  530. package/tests/e2e/feedback-customer.e2e.ts +226 -0
  531. package/tests/e2e/feedback-management.e2e.ts +565 -0
  532. package/tests/e2e/feedback-submit.e2e.ts +133 -0
  533. package/tests/e2e/feedback-view.e2e.ts +297 -0
  534. package/tests/e2e/fixtures/test-data.ts +235 -0
  535. package/tests/e2e/health-check.e2e.ts +230 -0
  536. package/tests/e2e/helpers/test-utils-helpers.test.ts +43 -0
  537. package/tests/e2e/helpers/test-utils.ts +298 -0
  538. package/tests/e2e/integration-placeholders.e2e.ts +199 -0
  539. package/tests/e2e/organization.e2e.ts +292 -0
  540. package/tests/e2e/permissions.e2e.ts +424 -0
  541. package/tests/e2e/project-widget.e2e.ts +63 -0
  542. package/tests/feedback/filters.test.ts +29 -0
  543. package/tests/hooks/use-permissions.test.ts +52 -0
  544. package/tests/lib/ai/classifier.test.ts +104 -0
  545. package/tests/lib/ai/duplicate-detector.test.ts +234 -0
  546. package/tests/lib/attachments-schema.test.ts +30 -0
  547. package/tests/lib/auth/session.test.ts +49 -0
  548. package/tests/lib/auth-client.test.ts +37 -0
  549. package/tests/lib/auth-config.test.ts +26 -0
  550. package/tests/lib/feedback-prefill.test.ts +52 -0
  551. package/tests/lib/feedback-processor.test.ts +41 -0
  552. package/tests/lib/feedback-schema.test.ts +33 -0
  553. package/tests/lib/file-validator.test.ts +48 -0
  554. package/tests/lib/get-feedback-by-id.test.ts +37 -0
  555. package/tests/lib/invitations.test.ts +35 -0
  556. package/tests/lib/login-schema.test.ts +36 -0
  557. package/tests/lib/org-context.test.ts +95 -0
  558. package/tests/lib/organization-access.test.ts +44 -0
  559. package/tests/lib/organization-member-role-schema.test.ts +41 -0
  560. package/tests/lib/permissions.test.ts +88 -0
  561. package/tests/lib/portal-analytics.test.ts +25 -0
  562. package/tests/lib/portal-contributors.test.ts +25 -0
  563. package/tests/lib/portal-copy.test.ts +27 -0
  564. package/tests/lib/portal-i18n.test.ts +30 -0
  565. package/tests/lib/portal-leaderboard-settings.test.ts +25 -0
  566. package/tests/lib/portal-modules.test.ts +25 -0
  567. package/tests/lib/portal-seo.test.ts +25 -0
  568. package/tests/lib/portal-sharing.test.ts +25 -0
  569. package/tests/lib/portal-sorting.test.ts +25 -0
  570. package/tests/lib/portal-theme.test.ts +25 -0
  571. package/tests/lib/rate-limit.test.ts +142 -0
  572. package/tests/lib/resolve-locale.test.ts +34 -0
  573. package/tests/lib/services/backup.test.ts +145 -0
  574. package/tests/lib/user-organizations.test.ts +42 -0
  575. package/tests/lib/user-role-schema.test.ts +33 -0
  576. package/tests/lib/user-schema.test.ts +25 -0
  577. package/tests/setup.ts +74 -0
  578. package/tsconfig.json +34 -0
  579. package/types/bun-test.d.ts +31 -0
@@ -0,0 +1,152 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import Link from "next/link";
22
+ import { useTranslations } from "next-intl";
23
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
24
+ import { Badge } from "@/components/ui/badge";
25
+ import { Button } from "@/components/ui/button";
26
+ import { ArrowRight, Inbox } from "lucide-react";
27
+
28
+ interface FeedbackItem {
29
+ feedbackId: number;
30
+ title: string;
31
+ type: string;
32
+ status: string;
33
+ priority: string;
34
+ createdAt: Date;
35
+ }
36
+
37
+ interface RecentFeedbackListProps {
38
+ feedback: FeedbackItem[];
39
+ }
40
+
41
+ const statusColors: Record<string, string> = {
42
+ new: "bg-blue-100 text-blue-800",
43
+ in_progress: "bg-yellow-100 text-yellow-800",
44
+ resolved: "bg-green-100 text-green-800",
45
+ closed: "bg-gray-100 text-gray-800",
46
+ };
47
+
48
+ const priorityColors: Record<string, string> = {
49
+ low: "bg-slate-100 text-slate-800",
50
+ medium: "bg-orange-100 text-orange-800",
51
+ high: "bg-red-100 text-red-800",
52
+ critical: "bg-red-200 text-red-900",
53
+ };
54
+
55
+ export function RecentFeedbackList({ feedback }: RecentFeedbackListProps) {
56
+ const t = useTranslations("dashboard.recentFeedback");
57
+ const tFeedback = useTranslations("feedback");
58
+ const tCommon = useTranslations("common");
59
+
60
+ const getStatusLabel = (status: string) => {
61
+ const key = status === "in_progress" ? "inProgress" : status;
62
+ return tFeedback(`status.${key}`) || status;
63
+ };
64
+
65
+ const getTypeLabel = (type: string) => {
66
+ return tFeedback(`type.${type}`) || type;
67
+ };
68
+
69
+ const getPriorityLabel = (priority: string) => {
70
+ return tFeedback(`priority.${priority}`) || priority;
71
+ };
72
+
73
+ const formatDate = (date: Date): string => {
74
+ const d = new Date(date);
75
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
76
+ const month = months[d.getMonth()];
77
+ const day = d.getDate();
78
+ const hours = d.getHours();
79
+ const minutes = d.getMinutes().toString().padStart(2, "0");
80
+ const ampm = hours >= 12 ? "PM" : "AM";
81
+ const hour12 = hours % 12 || 12;
82
+ return `${month} ${day}, ${hour12}:${minutes} ${ampm}`;
83
+ };
84
+
85
+ if (feedback.length === 0) {
86
+ return (
87
+ <Card>
88
+ <CardHeader>
89
+ <CardTitle>{t("title")}</CardTitle>
90
+ </CardHeader>
91
+ <CardContent>
92
+ <div className="flex flex-col items-center justify-center py-8 text-center">
93
+ <Inbox className="h-12 w-12 text-muted-foreground mb-4" />
94
+ <p className="text-muted-foreground mb-4">{t("noData")}</p>
95
+ <Button asChild>
96
+ <Link href="/admin/feedback/new">{tCommon("submitFirst")}</Link>
97
+ </Button>
98
+ </div>
99
+ </CardContent>
100
+ </Card>
101
+ );
102
+ }
103
+
104
+ return (
105
+ <Card>
106
+ <CardHeader className="flex flex-row items-center justify-between">
107
+ <CardTitle>{t("title")}</CardTitle>
108
+ <Button variant="ghost" size="sm" asChild>
109
+ <Link href="/admin/feedback">
110
+ {tCommon("viewAll")} <ArrowRight className="ml-1 h-4 w-4" />
111
+ </Link>
112
+ </Button>
113
+ </CardHeader>
114
+ <CardContent>
115
+ <div className="space-y-4">
116
+ {feedback.map((item) => (
117
+ <Link
118
+ key={item.feedbackId}
119
+ href={`/admin/feedback/${item.feedbackId}`}
120
+ className="block"
121
+ >
122
+ <div className="flex items-center justify-between rounded-lg border p-3 hover:bg-muted/50 transition-colors">
123
+ <div className="space-y-1 flex-1 min-w-0">
124
+ <p className="font-medium truncate">{item.title}</p>
125
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
126
+ <span>{getTypeLabel(item.type)}</span>
127
+ <span>·</span>
128
+ <span>{formatDate(item.createdAt)}</span>
129
+ </div>
130
+ </div>
131
+ <div className="flex items-center gap-2 ml-4">
132
+ <Badge
133
+ variant="secondary"
134
+ className={priorityColors[item.priority] || ""}
135
+ >
136
+ {getPriorityLabel(item.priority)}
137
+ </Badge>
138
+ <Badge
139
+ variant="secondary"
140
+ className={statusColors[item.status] || ""}
141
+ >
142
+ {getStatusLabel(item.status)}
143
+ </Badge>
144
+ </div>
145
+ </div>
146
+ </Link>
147
+ ))}
148
+ </div>
149
+ </CardContent>
150
+ </Card>
151
+ );
152
+ }
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import { useTranslations } from "next-intl";
22
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
23
+ import { MessageSquare, Clock, TrendingUp, CheckCircle } from "lucide-react";
24
+
25
+ interface StatsCardsProps {
26
+ totalFeedback: number;
27
+ pendingFeedback: number;
28
+ weeklyFeedback: number;
29
+ resolvedFeedback: number;
30
+ }
31
+
32
+ export function StatsCards({
33
+ totalFeedback,
34
+ pendingFeedback,
35
+ weeklyFeedback,
36
+ resolvedFeedback,
37
+ }: StatsCardsProps) {
38
+ const t = useTranslations("dashboard.stats");
39
+
40
+ const stats = [
41
+ {
42
+ title: t("totalFeedback"),
43
+ value: totalFeedback,
44
+ icon: MessageSquare,
45
+ color: "text-blue-600",
46
+ bgColor: "bg-blue-100",
47
+ },
48
+ {
49
+ title: t("pending"),
50
+ value: pendingFeedback,
51
+ icon: Clock,
52
+ color: "text-orange-600",
53
+ bgColor: "bg-orange-100",
54
+ },
55
+ {
56
+ title: t("weeklyNew"),
57
+ value: weeklyFeedback,
58
+ icon: TrendingUp,
59
+ color: "text-green-600",
60
+ bgColor: "bg-green-100",
61
+ },
62
+ {
63
+ title: t("resolved"),
64
+ value: resolvedFeedback,
65
+ icon: CheckCircle,
66
+ color: "text-purple-600",
67
+ bgColor: "bg-purple-100",
68
+ },
69
+ ];
70
+
71
+ return (
72
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
73
+ {stats.map((stat) => (
74
+ <Card key={stat.title}>
75
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
76
+ <CardTitle className="text-sm font-medium">{stat.title}</CardTitle>
77
+ <div className={`rounded-full p-2 ${stat.bgColor}`}>
78
+ <stat.icon className={`h-4 w-4 ${stat.color}`} />
79
+ </div>
80
+ </CardHeader>
81
+ <CardContent>
82
+ <div className="text-2xl font-bold">{stat.value}</div>
83
+ </CardContent>
84
+ </Card>
85
+ ))}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import { useTranslations } from "next-intl";
22
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
23
+ import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts";
24
+
25
+ interface StatusChartProps {
26
+ data: { status: string; count: number }[];
27
+ }
28
+
29
+ const STATUS_COLORS: Record<string, string> = {
30
+ new: "#3b82f6",
31
+ "in-progress": "#f59e0b",
32
+ resolved: "#22c55e",
33
+ closed: "#6b7280",
34
+ };
35
+
36
+ export function StatusChart({ data }: StatusChartProps) {
37
+ const t = useTranslations("dashboard.statusChart");
38
+ const tFeedback = useTranslations("feedback");
39
+
40
+ const normalizeStatus = (status: string) =>
41
+ status === "in_progress" ? "in-progress" : status;
42
+
43
+ const getStatusLabel = (status: string) => {
44
+ const normalizedStatus = normalizeStatus(status);
45
+ const key = normalizedStatus === "in-progress" ? "inProgress" : normalizedStatus;
46
+ return tFeedback(`status.${key}`) || status;
47
+ };
48
+
49
+ const chartData = data.map((item) => ({
50
+ name: getStatusLabel(item.status),
51
+ value: item.count,
52
+ color: STATUS_COLORS[normalizeStatus(item.status)] || "#94a3b8",
53
+ }));
54
+
55
+ const total = chartData.reduce((sum, item) => sum + item.value, 0);
56
+
57
+ if (total === 0) {
58
+ return (
59
+ <Card>
60
+ <CardHeader>
61
+ <CardTitle>{t("title")}</CardTitle>
62
+ </CardHeader>
63
+ <CardContent>
64
+ <div className="flex items-center justify-center h-[200px] text-muted-foreground">
65
+ {t("noData")}
66
+ </div>
67
+ </CardContent>
68
+ </Card>
69
+ );
70
+ }
71
+
72
+ return (
73
+ <Card>
74
+ <CardHeader>
75
+ <CardTitle>{t("title")}</CardTitle>
76
+ </CardHeader>
77
+ <CardContent>
78
+ <div className="h-[250px]">
79
+ <ResponsiveContainer width="100%" height="100%">
80
+ <PieChart>
81
+ <Pie
82
+ data={chartData}
83
+ cx="50%"
84
+ cy="50%"
85
+ innerRadius={50}
86
+ outerRadius={80}
87
+ paddingAngle={2}
88
+ dataKey="value"
89
+ >
90
+ {chartData.map((entry, index) => (
91
+ <Cell key={`cell-${index}`} fill={entry.color} />
92
+ ))}
93
+ </Pie>
94
+ <Tooltip
95
+ formatter={(value: number | undefined) => [`${value}`, tFeedback("filters.title")]}
96
+ />
97
+ <Legend
98
+ formatter={(value) => <span className="text-sm">{value}</span>}
99
+ />
100
+ </PieChart>
101
+ </ResponsiveContainer>
102
+ </div>
103
+ </CardContent>
104
+ </Card>
105
+ );
106
+ }
@@ -0,0 +1,70 @@
1
+ /*
2
+ * Copyright (c) 2026 Echo Team
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published by
6
+ * the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { cn } from "@/lib/utils"
19
+
20
+ function ExampleWrapper({ className, ...props }: React.ComponentProps<"div">) {
21
+ return (
22
+ <div className="bg-background w-full">
23
+ <div
24
+ data-slot="example-wrapper"
25
+ className={cn(
26
+ "mx-auto grid min-h-screen w-full max-w-5xl min-w-0 content-center items-start gap-8 p-4 pt-2 sm:gap-12 sm:p-6 md:grid-cols-2 md:gap-8 lg:p-12 2xl:max-w-6xl",
27
+ className
28
+ )}
29
+ {...props}
30
+ />
31
+ </div>
32
+ )
33
+ }
34
+
35
+ function Example({
36
+ title,
37
+ children,
38
+ className,
39
+ containerClassName,
40
+ ...props
41
+ }: React.ComponentProps<"div"> & {
42
+ title: string
43
+ containerClassName?: string
44
+ }) {
45
+ return (
46
+ <div
47
+ data-slot="example"
48
+ className={cn(
49
+ "mx-auto flex w-full max-w-lg min-w-0 flex-col gap-1 self-stretch lg:max-w-none",
50
+ containerClassName
51
+ )}
52
+ {...props}
53
+ >
54
+ <div className="text-muted-foreground px-1.5 py-2 text-xs font-medium">
55
+ {title}
56
+ </div>
57
+ <div
58
+ data-slot="example-content"
59
+ className={cn(
60
+ "bg-background text-foreground flex min-w-0 flex-1 flex-col items-start gap-6 border border-dashed p-4 sm:p-6 *:[div:not([class*='w-'])]:w-full",
61
+ className
62
+ )}
63
+ >
64
+ {children}
65
+ </div>
66
+ </div>
67
+ )
68
+ }
69
+
70
+ export { ExampleWrapper, Example }
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import { Button } from "@/components/ui/button";
22
+ import { Card } from "@/components/ui/card";
23
+ import { File, Download, Image as ImageIcon, FileText, FileSpreadsheet } from "lucide-react";
24
+ import { formatFileSize } from "@/lib/utils/format";
25
+
26
+ interface Attachment {
27
+ attachmentId: number;
28
+ fileName: string;
29
+ filePath: string;
30
+ fileSize: number;
31
+ mimeType: string;
32
+ createdAt: string;
33
+ }
34
+
35
+ interface AttachmentListProps {
36
+ attachments: Attachment[];
37
+ className?: string;
38
+ }
39
+
40
+ export function AttachmentList({ attachments, className }: AttachmentListProps) {
41
+ if (attachments.length === 0) {
42
+ return (
43
+ <div className="text-center text-muted-foreground py-4">
44
+ 暂无附件
45
+ </div>
46
+ );
47
+ }
48
+
49
+ const getFileIcon = (mimeType: string) => {
50
+ if (mimeType.startsWith("image/")) {
51
+ return <ImageIcon className="w-5 h-5 text-blue-500" />;
52
+ }
53
+ if (mimeType.includes("pdf")) {
54
+ return <FileText className="w-5 h-5 text-red-500" />;
55
+ }
56
+ if (mimeType.includes("word")) {
57
+ return <FileText className="w-5 h-5 text-blue-600" />;
58
+ }
59
+ if (mimeType.includes("excel") || mimeType.includes("spreadsheet")) {
60
+ return <FileSpreadsheet className="w-5 h-5 text-green-600" />;
61
+ }
62
+ return <File className="w-5 h-5 text-gray-500" />;
63
+ };
64
+
65
+ return (
66
+ <div className={className}>
67
+ <div className="space-y-2">
68
+ {attachments.map((attachment) => (
69
+ <Card key={attachment.attachmentId} className="p-3">
70
+ <div className="flex items-center justify-between">
71
+ <div className="flex items-center gap-3 flex-1 min-w-0">
72
+ <span className="shrink-0">
73
+ {getFileIcon(attachment.mimeType)}
74
+ </span>
75
+ <div className="flex-1 min-w-0">
76
+ <p className="font-medium truncate">{attachment.fileName}</p>
77
+ <p className="text-sm text-muted-foreground">
78
+ {formatFileSize(attachment.fileSize)}
79
+ </p>
80
+ </div>
81
+ </div>
82
+
83
+ <Button
84
+ variant="outline"
85
+ size="sm"
86
+ asChild
87
+ >
88
+ <a
89
+ href={`/${attachment.filePath}`}
90
+ download={attachment.fileName}
91
+ className="flex items-center gap-2"
92
+ >
93
+ <Download className="w-4 h-4" />
94
+ 下载
95
+ </a>
96
+ </Button>
97
+ </div>
98
+ </Card>
99
+ ))}
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import { Badge } from "@/components/ui/badge";
22
+ import {
23
+ Tooltip,
24
+ TooltipContent,
25
+ TooltipProvider,
26
+ TooltipTrigger,
27
+ } from "@/components/ui/tooltip";
28
+ import { Bot } from "lucide-react";
29
+ import { useTranslations } from "next-intl";
30
+
31
+ interface AutoClassificationBadgeProps {
32
+ classification?: {
33
+ type: string;
34
+ priority: string;
35
+ confidence: number;
36
+ reasons: string[];
37
+ };
38
+ }
39
+
40
+ export function AutoClassificationBadge({
41
+ classification,
42
+ }: AutoClassificationBadgeProps) {
43
+ const t = useTranslations("portal.ai");
44
+ if (!classification) return null;
45
+
46
+ const confidenceColor =
47
+ classification.confidence > 0.7
48
+ ? "text-green-600"
49
+ : classification.confidence > 0.4
50
+ ? "text-yellow-600"
51
+ : "text-gray-600";
52
+
53
+ return (
54
+ <TooltipProvider>
55
+ <Tooltip>
56
+ <TooltipTrigger asChild>
57
+ <Badge variant="outline" className="cursor-help gap-1">
58
+ <Bot className="h-3 w-3" />
59
+ Auto-classified
60
+ <span className={confidenceColor}>
61
+ ({Math.round(classification.confidence * 100)}%)
62
+ </span>
63
+ </Badge>
64
+ </TooltipTrigger>
65
+ <TooltipContent side="bottom" className="max-w-xs">
66
+ <div className="space-y-2">
67
+ <p className="font-medium">{t("classification")}</p>
68
+ <div className="space-y-1 text-sm">
69
+ <p>
70
+ Type: <span className="capitalize">{classification.type}</span>
71
+ </p>
72
+ <p>
73
+ Priority:{" "}
74
+ <span className="capitalize">{classification.priority}</span>
75
+ </p>
76
+ {classification.reasons.length > 0 && (
77
+ <div className="border-t pt-2">
78
+ <p className="text-muted-foreground mb-1 text-xs">{t("reasons")}</p>
79
+ <ul className="space-y-1 text-xs">
80
+ {classification.reasons.map((reason, i) => (
81
+ <li key={i}>• {reason}</li>
82
+ ))}
83
+ </ul>
84
+ </div>
85
+ )}
86
+ </div>
87
+ </div>
88
+ </TooltipContent>
89
+ </Tooltip>
90
+ </TooltipProvider>
91
+ );
92
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+
4
+ /*
5
+ * Copyright (c) 2026 Echo Team
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ import { useState } from "react";
22
+ import { Button } from "@/components/ui/button";
23
+ import { RefreshCw } from "lucide-react";
24
+
25
+ interface ClassificationOverrideProps {
26
+ feedbackId: number;
27
+ onUpdated: () => void;
28
+ }
29
+
30
+ export function ClassificationOverride({
31
+ feedbackId,
32
+ onUpdated,
33
+ }: ClassificationOverrideProps) {
34
+ const [isReclassifying, setIsReclassifying] = useState(false);
35
+
36
+ async function reclassify() {
37
+ setIsReclassifying(true);
38
+ try {
39
+ const response = await fetch(`/api/feedback/${feedbackId}/reclassify`, {
40
+ method: "POST",
41
+ });
42
+
43
+ if (response.ok) {
44
+ onUpdated();
45
+ }
46
+ } finally {
47
+ setIsReclassifying(false);
48
+ }
49
+ }
50
+
51
+ return (
52
+ <Button
53
+ variant="ghost"
54
+ size="sm"
55
+ onClick={reclassify}
56
+ disabled={isReclassifying}
57
+ >
58
+ <RefreshCw
59
+ className={`mr-2 h-4 w-4 ${isReclassifying ? "animate-spin" : ""}`}
60
+ />
61
+ Re-run Classification
62
+ </Button>
63
+ );
64
+ }