@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,519 @@
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
+ "use client"
19
+
20
+ import * as React from "react"
21
+ import Image from "next/image"
22
+
23
+ import {
24
+ Example,
25
+ ExampleWrapper,
26
+ } from "@/components/example"
27
+ import {
28
+ AlertDialog,
29
+ AlertDialogAction,
30
+ AlertDialogCancel,
31
+ AlertDialogContent,
32
+ AlertDialogDescription,
33
+ AlertDialogFooter,
34
+ AlertDialogHeader,
35
+ AlertDialogMedia,
36
+ AlertDialogTitle,
37
+ AlertDialogTrigger,
38
+ } from "@/components/ui/alert-dialog"
39
+ import { Badge } from "@/components/ui/badge"
40
+ import { Button } from "@/components/ui/button"
41
+ import {
42
+ Card,
43
+ CardAction,
44
+ CardContent,
45
+ CardDescription,
46
+ CardFooter,
47
+ CardHeader,
48
+ CardTitle,
49
+ } from "@/components/ui/card"
50
+ import {
51
+ Combobox,
52
+ ComboboxContent,
53
+ ComboboxEmpty,
54
+ ComboboxInput,
55
+ ComboboxItem,
56
+ ComboboxList,
57
+ } from "@/components/ui/combobox"
58
+ import {
59
+ DropdownMenu,
60
+ DropdownMenuCheckboxItem,
61
+ DropdownMenuContent,
62
+ DropdownMenuGroup,
63
+ DropdownMenuItem,
64
+ DropdownMenuLabel,
65
+ DropdownMenuPortal,
66
+ DropdownMenuRadioGroup,
67
+ DropdownMenuRadioItem,
68
+ DropdownMenuSeparator,
69
+ DropdownMenuShortcut,
70
+ DropdownMenuSub,
71
+ DropdownMenuSubContent,
72
+ DropdownMenuSubTrigger,
73
+ DropdownMenuTrigger,
74
+ } from "@/components/ui/dropdown-menu"
75
+ import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
76
+ import { Input } from "@/components/ui/input"
77
+ import {
78
+ Select,
79
+ SelectContent,
80
+ SelectGroup,
81
+ SelectItem,
82
+ SelectTrigger,
83
+ SelectValue,
84
+ } from "@/components/ui/select"
85
+ import { Textarea } from "@/components/ui/textarea"
86
+ import { PlusIcon, BluetoothIcon, MoreVerticalIcon, FileIcon, FolderIcon, FolderOpenIcon, FileCodeIcon, MoreHorizontalIcon, FolderSearchIcon, SaveIcon, DownloadIcon, EyeIcon, LayoutIcon, PaletteIcon, SunIcon, MoonIcon, MonitorIcon, UserIcon, CreditCardIcon, SettingsIcon, KeyboardIcon, LanguagesIcon, BellIcon, MailIcon, ShieldIcon, HelpCircleIcon, FileTextIcon, LogOutIcon } from "lucide-react"
87
+
88
+ export function ComponentExample() {
89
+ return (
90
+ <ExampleWrapper>
91
+ <CardExample />
92
+ <FormExample />
93
+ </ExampleWrapper>
94
+ )
95
+ }
96
+
97
+ function CardExample() {
98
+ const [isOpen, setIsOpen] = React.useState(false)
99
+
100
+ return (
101
+ <Example title="Card" className="items-center justify-center">
102
+ <Card className="relative w-full max-w-sm overflow-hidden pt-0">
103
+ <div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
104
+ <div className="relative z-20 aspect-video w-full">
105
+ <Image
106
+ src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
107
+ alt="Photo by mymind on Unsplash"
108
+ title="Photo by mymind on Unsplash"
109
+ fill
110
+ sizes="(min-width: 640px) 24rem, 100vw"
111
+ className="object-cover brightness-60 grayscale"
112
+ />
113
+ </div>
114
+ <CardHeader>
115
+ <CardTitle>Observability Plus is replacing Monitoring</CardTitle>
116
+ <CardDescription>
117
+ Switch to the improved way to explore your data, with natural
118
+ language. Monitoring will no longer be available on the Pro plan in
119
+ November, 2025
120
+ </CardDescription>
121
+ </CardHeader>
122
+ <CardFooter>
123
+ <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
124
+ <AlertDialogTrigger asChild>
125
+ <Button>
126
+ <PlusIcon data-icon="inline-start" />
127
+ Show Dialog
128
+ </Button>
129
+ </AlertDialogTrigger>
130
+ <AlertDialogContent size="sm">
131
+ <AlertDialogHeader>
132
+ <AlertDialogMedia>
133
+ <BluetoothIcon
134
+ />
135
+ </AlertDialogMedia>
136
+ <AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
137
+ <AlertDialogDescription>
138
+ Do you want to allow the USB accessory to connect to this
139
+ device?
140
+ </AlertDialogDescription>
141
+ </AlertDialogHeader>
142
+ <AlertDialogFooter>
143
+ <AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
144
+ <AlertDialogAction>Allow</AlertDialogAction>
145
+ </AlertDialogFooter>
146
+ </AlertDialogContent>
147
+ </AlertDialog>
148
+ <Badge variant="secondary" className="ml-auto">
149
+ Warning
150
+ </Badge>
151
+ </CardFooter>
152
+ </Card>
153
+ </Example>
154
+ )
155
+ }
156
+
157
+ const frameworks = [
158
+ "Next.js",
159
+ "SvelteKit",
160
+ "Nuxt.js",
161
+ "Remix",
162
+ "Astro",
163
+ ] as const
164
+
165
+ function FormExample() {
166
+ const [notifications, setNotifications] = React.useState({
167
+ email: true,
168
+ sms: false,
169
+ push: true,
170
+ })
171
+ const [theme, setTheme] = React.useState("light")
172
+
173
+ return (
174
+ <Example title="Form">
175
+ <Card className="w-full max-w-md">
176
+ <CardHeader>
177
+ <CardTitle>User Information</CardTitle>
178
+ <CardDescription>Please fill in your details below</CardDescription>
179
+ <CardAction>
180
+ <DropdownMenu>
181
+ <DropdownMenuTrigger asChild>
182
+ <Button variant="ghost" size="icon">
183
+ <MoreVerticalIcon
184
+ />
185
+ <span className="sr-only">More options</span>
186
+ </Button>
187
+ </DropdownMenuTrigger>
188
+ <DropdownMenuContent align="end" className="w-56">
189
+ <DropdownMenuGroup>
190
+ <DropdownMenuLabel>File</DropdownMenuLabel>
191
+ <DropdownMenuItem>
192
+ <FileIcon
193
+ />
194
+ New File
195
+ <DropdownMenuShortcut>⌘N</DropdownMenuShortcut>
196
+ </DropdownMenuItem>
197
+ <DropdownMenuItem>
198
+ <FolderIcon
199
+ />
200
+ New Folder
201
+ <DropdownMenuShortcut>⇧⌘N</DropdownMenuShortcut>
202
+ </DropdownMenuItem>
203
+ <DropdownMenuSub>
204
+ <DropdownMenuSubTrigger>
205
+ <FolderOpenIcon
206
+ />
207
+ Open Recent
208
+ </DropdownMenuSubTrigger>
209
+ <DropdownMenuPortal>
210
+ <DropdownMenuSubContent>
211
+ <DropdownMenuGroup>
212
+ <DropdownMenuLabel>Recent Projects</DropdownMenuLabel>
213
+ <DropdownMenuItem>
214
+ <FileCodeIcon
215
+ />
216
+ Project Alpha
217
+ </DropdownMenuItem>
218
+ <DropdownMenuItem>
219
+ <FileCodeIcon
220
+ />
221
+ Project Beta
222
+ </DropdownMenuItem>
223
+ <DropdownMenuSub>
224
+ <DropdownMenuSubTrigger>
225
+ <MoreHorizontalIcon
226
+ />
227
+ More Projects
228
+ </DropdownMenuSubTrigger>
229
+ <DropdownMenuPortal>
230
+ <DropdownMenuSubContent>
231
+ <DropdownMenuItem>
232
+ <FileCodeIcon
233
+ />
234
+ Project Gamma
235
+ </DropdownMenuItem>
236
+ <DropdownMenuItem>
237
+ <FileCodeIcon
238
+ />
239
+ Project Delta
240
+ </DropdownMenuItem>
241
+ </DropdownMenuSubContent>
242
+ </DropdownMenuPortal>
243
+ </DropdownMenuSub>
244
+ </DropdownMenuGroup>
245
+ <DropdownMenuSeparator />
246
+ <DropdownMenuGroup>
247
+ <DropdownMenuItem>
248
+ <FolderSearchIcon
249
+ />
250
+ Browse...
251
+ </DropdownMenuItem>
252
+ </DropdownMenuGroup>
253
+ </DropdownMenuSubContent>
254
+ </DropdownMenuPortal>
255
+ </DropdownMenuSub>
256
+ <DropdownMenuSeparator />
257
+ <DropdownMenuItem>
258
+ <SaveIcon
259
+ />
260
+ Save
261
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
262
+ </DropdownMenuItem>
263
+ <DropdownMenuItem>
264
+ <DownloadIcon
265
+ />
266
+ Export
267
+ <DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
268
+ </DropdownMenuItem>
269
+ </DropdownMenuGroup>
270
+ <DropdownMenuSeparator />
271
+ <DropdownMenuGroup>
272
+ <DropdownMenuLabel>View</DropdownMenuLabel>
273
+ <DropdownMenuCheckboxItem
274
+ checked={notifications.email}
275
+ onCheckedChange={(checked) =>
276
+ setNotifications({
277
+ ...notifications,
278
+ email: checked === true,
279
+ })
280
+ }
281
+ >
282
+ <EyeIcon
283
+ />
284
+ Show Sidebar
285
+ </DropdownMenuCheckboxItem>
286
+ <DropdownMenuCheckboxItem
287
+ checked={notifications.sms}
288
+ onCheckedChange={(checked) =>
289
+ setNotifications({
290
+ ...notifications,
291
+ sms: checked === true,
292
+ })
293
+ }
294
+ >
295
+ <LayoutIcon
296
+ />
297
+ Show Status Bar
298
+ </DropdownMenuCheckboxItem>
299
+ <DropdownMenuSub>
300
+ <DropdownMenuSubTrigger>
301
+ <PaletteIcon
302
+ />
303
+ Theme
304
+ </DropdownMenuSubTrigger>
305
+ <DropdownMenuPortal>
306
+ <DropdownMenuSubContent>
307
+ <DropdownMenuGroup>
308
+ <DropdownMenuLabel>Appearance</DropdownMenuLabel>
309
+ <DropdownMenuRadioGroup
310
+ value={theme}
311
+ onValueChange={setTheme}
312
+ >
313
+ <DropdownMenuRadioItem value="light">
314
+ <SunIcon
315
+ />
316
+ Light
317
+ </DropdownMenuRadioItem>
318
+ <DropdownMenuRadioItem value="dark">
319
+ <MoonIcon
320
+ />
321
+ Dark
322
+ </DropdownMenuRadioItem>
323
+ <DropdownMenuRadioItem value="system">
324
+ <MonitorIcon
325
+ />
326
+ System
327
+ </DropdownMenuRadioItem>
328
+ </DropdownMenuRadioGroup>
329
+ </DropdownMenuGroup>
330
+ </DropdownMenuSubContent>
331
+ </DropdownMenuPortal>
332
+ </DropdownMenuSub>
333
+ </DropdownMenuGroup>
334
+ <DropdownMenuSeparator />
335
+ <DropdownMenuGroup>
336
+ <DropdownMenuLabel>Account</DropdownMenuLabel>
337
+ <DropdownMenuItem>
338
+ <UserIcon
339
+ />
340
+ Profile
341
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
342
+ </DropdownMenuItem>
343
+ <DropdownMenuItem>
344
+ <CreditCardIcon
345
+ />
346
+ Billing
347
+ </DropdownMenuItem>
348
+ <DropdownMenuSub>
349
+ <DropdownMenuSubTrigger>
350
+ <SettingsIcon
351
+ />
352
+ Settings
353
+ </DropdownMenuSubTrigger>
354
+ <DropdownMenuPortal>
355
+ <DropdownMenuSubContent>
356
+ <DropdownMenuGroup>
357
+ <DropdownMenuLabel>Preferences</DropdownMenuLabel>
358
+ <DropdownMenuItem>
359
+ <KeyboardIcon
360
+ />
361
+ Keyboard Shortcuts
362
+ </DropdownMenuItem>
363
+ <DropdownMenuItem>
364
+ <LanguagesIcon
365
+ />
366
+ Language
367
+ </DropdownMenuItem>
368
+ <DropdownMenuSub>
369
+ <DropdownMenuSubTrigger>
370
+ <BellIcon
371
+ />
372
+ Notifications
373
+ </DropdownMenuSubTrigger>
374
+ <DropdownMenuPortal>
375
+ <DropdownMenuSubContent>
376
+ <DropdownMenuGroup>
377
+ <DropdownMenuLabel>
378
+ Notification Types
379
+ </DropdownMenuLabel>
380
+ <DropdownMenuCheckboxItem
381
+ checked={notifications.push}
382
+ onCheckedChange={(checked) =>
383
+ setNotifications({
384
+ ...notifications,
385
+ push: checked === true,
386
+ })
387
+ }
388
+ >
389
+ <BellIcon
390
+ />
391
+ Push Notifications
392
+ </DropdownMenuCheckboxItem>
393
+ <DropdownMenuCheckboxItem
394
+ checked={notifications.email}
395
+ onCheckedChange={(checked) =>
396
+ setNotifications({
397
+ ...notifications,
398
+ email: checked === true,
399
+ })
400
+ }
401
+ >
402
+ <MailIcon
403
+ />
404
+ Email Notifications
405
+ </DropdownMenuCheckboxItem>
406
+ </DropdownMenuGroup>
407
+ </DropdownMenuSubContent>
408
+ </DropdownMenuPortal>
409
+ </DropdownMenuSub>
410
+ </DropdownMenuGroup>
411
+ <DropdownMenuSeparator />
412
+ <DropdownMenuGroup>
413
+ <DropdownMenuItem>
414
+ <ShieldIcon
415
+ />
416
+ Privacy & Security
417
+ </DropdownMenuItem>
418
+ </DropdownMenuGroup>
419
+ </DropdownMenuSubContent>
420
+ </DropdownMenuPortal>
421
+ </DropdownMenuSub>
422
+ </DropdownMenuGroup>
423
+ <DropdownMenuSeparator />
424
+ <DropdownMenuGroup>
425
+ <DropdownMenuItem>
426
+ <HelpCircleIcon
427
+ />
428
+ Help & Support
429
+ </DropdownMenuItem>
430
+ <DropdownMenuItem>
431
+ <FileTextIcon
432
+ />
433
+ Documentation
434
+ </DropdownMenuItem>
435
+ </DropdownMenuGroup>
436
+ <DropdownMenuSeparator />
437
+ <DropdownMenuGroup>
438
+ <DropdownMenuItem variant="destructive">
439
+ <LogOutIcon
440
+ />
441
+ Sign Out
442
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
443
+ </DropdownMenuItem>
444
+ </DropdownMenuGroup>
445
+ </DropdownMenuContent>
446
+ </DropdownMenu>
447
+ </CardAction>
448
+ </CardHeader>
449
+ <CardContent>
450
+ <form>
451
+ <FieldGroup>
452
+ <div className="grid grid-cols-2 gap-4">
453
+ <Field>
454
+ <FieldLabel htmlFor="small-form-name">Name</FieldLabel>
455
+ <Input
456
+ id="small-form-name"
457
+ placeholder="Enter your name"
458
+ required
459
+ />
460
+ </Field>
461
+ <Field>
462
+ <FieldLabel htmlFor="small-form-role">Role</FieldLabel>
463
+ <Select defaultValue="">
464
+ <SelectTrigger id="small-form-role">
465
+ <SelectValue placeholder="Select a role" />
466
+ </SelectTrigger>
467
+ <SelectContent>
468
+ <SelectGroup>
469
+ <SelectItem value="developer">Developer</SelectItem>
470
+ <SelectItem value="designer">Designer</SelectItem>
471
+ <SelectItem value="manager">Manager</SelectItem>
472
+ <SelectItem value="other">Other</SelectItem>
473
+ </SelectGroup>
474
+ </SelectContent>
475
+ </Select>
476
+ </Field>
477
+ </div>
478
+ <Field>
479
+ <FieldLabel htmlFor="small-form-framework">
480
+ Framework
481
+ </FieldLabel>
482
+ <Combobox items={frameworks}>
483
+ <ComboboxInput
484
+ id="small-form-framework"
485
+ placeholder="Select a framework"
486
+ required
487
+ />
488
+ <ComboboxContent>
489
+ <ComboboxEmpty>No frameworks found.</ComboboxEmpty>
490
+ <ComboboxList>
491
+ {(item) => (
492
+ <ComboboxItem key={item} value={item}>
493
+ {item}
494
+ </ComboboxItem>
495
+ )}
496
+ </ComboboxList>
497
+ </ComboboxContent>
498
+ </Combobox>
499
+ </Field>
500
+ <Field>
501
+ <FieldLabel htmlFor="small-form-comments">Comments</FieldLabel>
502
+ <Textarea
503
+ id="small-form-comments"
504
+ placeholder="Add any additional comments"
505
+ />
506
+ </Field>
507
+ <Field orientation="horizontal">
508
+ <Button type="submit">Submit</Button>
509
+ <Button variant="outline" type="button">
510
+ Cancel
511
+ </Button>
512
+ </Field>
513
+ </FieldGroup>
514
+ </form>
515
+ </CardContent>
516
+ </Card>
517
+ </Example>
518
+ )
519
+ }
@@ -0,0 +1,22 @@
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
+ export { StatsCards } from "./stats-cards";
19
+ export { RecentFeedbackList } from "./recent-feedback-list";
20
+ export { StatusChart } from "./status-chart";
21
+ export { QuickActions } from "./quick-actions";
22
+ export { OrganizationSwitcher } from "./organization-switcher";
@@ -0,0 +1,96 @@
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 { useCallback, useEffect, useState } from "react";
22
+ import { useRouter, useSearchParams } from "next/navigation";
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from "@/components/ui/select";
30
+
31
+ type OrgOption = { id: string; name: string; slug: string; role: string };
32
+
33
+ type Props = {
34
+ organizations: OrgOption[];
35
+ currentOrgId: string;
36
+ shouldPersistDefault?: boolean;
37
+ };
38
+
39
+ const COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
40
+
41
+ export function OrganizationSwitcher({
42
+ organizations,
43
+ currentOrgId,
44
+ shouldPersistDefault = false,
45
+ }: Props) {
46
+ const [mounted, setMounted] = useState(false);
47
+ const [value, setValue] = useState(currentOrgId);
48
+ const router = useRouter();
49
+ const searchParams = useSearchParams();
50
+
51
+ // Set mounted state after hydration to avoid hydration mismatch
52
+ useEffect(() => {
53
+ // eslint-disable-next-line react-hooks/set-state-in-effect
54
+ setMounted(true);
55
+ }, []);
56
+
57
+ const persistOrganization = useCallback((orgId: string) => {
58
+ const params = new URLSearchParams(searchParams);
59
+ params.set("organizationId", orgId);
60
+ document.cookie = `orgId=${orgId};path=/;max-age=${COOKIE_MAX_AGE_SECONDS};samesite=lax`;
61
+ router.replace(`/dashboard?${params.toString()}`);
62
+ }, [router, searchParams]);
63
+
64
+ useEffect(() => {
65
+ if (!organizations.length || !shouldPersistDefault) return;
66
+ persistOrganization(currentOrgId);
67
+ }, [organizations.length, currentOrgId, persistOrganization, shouldPersistDefault]);
68
+
69
+ const handleChange = (orgId: string) => {
70
+ setValue(orgId);
71
+ persistOrganization(orgId);
72
+ };
73
+
74
+ if (!mounted) {
75
+ return (
76
+ <div className="flex h-9 w-[240px] items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50">
77
+ <span className="block truncate">{organizations.find(o => o.id === currentOrgId)?.name || "Select Organization"}</span>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <Select value={value} onValueChange={handleChange}>
84
+ <SelectTrigger className="w-[240px]">
85
+ <SelectValue placeholder="选择组织" />
86
+ </SelectTrigger>
87
+ <SelectContent>
88
+ {organizations.map((org) => (
89
+ <SelectItem key={org.id} value={org.id}>
90
+ {org.name}
91
+ </SelectItem>
92
+ ))}
93
+ </SelectContent>
94
+ </Select>
95
+ );
96
+ }
@@ -0,0 +1,57 @@
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 { Button } from "@/components/ui/button";
24
+ import { Plus, List, Settings, Building2 } from "lucide-react";
25
+
26
+ export function QuickActions() {
27
+ const t = useTranslations("dashboard.quickActions");
28
+
29
+ return (
30
+ <div className="flex flex-wrap gap-3">
31
+ <Button asChild>
32
+ <Link href="/admin/feedback/new">
33
+ <Plus className="mr-2 h-4 w-4" />
34
+ {t("submitFeedback")}
35
+ </Link>
36
+ </Button>
37
+ <Button variant="outline" asChild>
38
+ <Link href="/admin/feedback">
39
+ <List className="mr-2 h-4 w-4" />
40
+ {t("viewAllFeedback")}
41
+ </Link>
42
+ </Button>
43
+ <Button variant="outline" asChild>
44
+ <Link href="/settings/notifications">
45
+ <Settings className="mr-2 h-4 w-4" />
46
+ {t("settings")}
47
+ </Link>
48
+ </Button>
49
+ <Button variant="outline" asChild>
50
+ <Link href="/settings/organizations/new">
51
+ <Building2 className="mr-2 h-4 w-4" />
52
+ {t("organizationSettings")}
53
+ </Link>
54
+ </Button>
55
+ </div>
56
+ );
57
+ }