@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
package/Makefile ADDED
@@ -0,0 +1,77 @@
1
+ # Makefile
2
+
3
+ .PHONY: help dev build up down restart logs db-migrate db-backup
4
+
5
+ help: ## Show this help message
6
+ @echo 'Usage: make [target]'
7
+ @echo ''
8
+ @echo 'Available targets:'
9
+ @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
10
+
11
+ dev: ## Start development environment
12
+ docker-compose -f docker-compose.dev.yml up -d
13
+ @echo "Development environment started!"
14
+ @echo "App: http://localhost:3000"
15
+ @echo "Database: postgresql://echo:echo@localhost:5432/echo_dev"
16
+
17
+ build: ## Build production images
18
+ docker-compose build
19
+
20
+ up: ## Start production environment
21
+ docker-compose up -d
22
+ @echo "Production environment started!"
23
+ @echo "App: http://localhost:3000"
24
+
25
+ down: ## Stop all containers
26
+ docker-compose down
27
+
28
+ restart: ## Restart all containers
29
+ docker-compose restart
30
+
31
+ logs: ## Show logs from all containers
32
+ docker-compose logs -f
33
+
34
+ logs-app: ## Show logs from app container
35
+ docker-compose logs -f app
36
+
37
+ logs-db: ## Show logs from database container
38
+ docker-compose logs -f postgres
39
+
40
+ db-migrate: ## Run database migrations
41
+ docker-compose exec app bunx drizzle-kit migrate
42
+
43
+ db-backup: ## Backup database (Docker)
44
+ @mkdir -p backups
45
+ docker-compose exec postgres pg_dump -U echo echo > backups/backup-$$(date +%Y%m%d-%H%M%S).sql
46
+ @echo "Backup completed!"
47
+
48
+ db-restore: ## Restore database (usage: make db-restore FILE=backups/backup.sql)
49
+ docker-compose exec -T postgres psql -U echo echo < $(FILE)
50
+
51
+ backup: ## Create database backup (local)
52
+ @echo "Creating backup..."
53
+ ./scripts/backup-db.sh
54
+
55
+ backup-ts: ## Create database backup using TypeScript
56
+ bun run scripts/backup-db.ts
57
+
58
+ restore: ## Restore database (usage: make restore FILE=backups/echo-20240101-020000.sql.gz)
59
+ ./scripts/restore-db.sh $(FILE)
60
+
61
+ list-backups: ## List all backups
62
+ @echo "Available backups:"
63
+ @ls -lh backups/echo-*.sql.gz 2>/dev/null || echo "No backups found"
64
+
65
+ db-shell: ## Open database shell
66
+ docker-compose exec postgres psql -U echo echo
67
+
68
+ shell: ## Open app container shell
69
+ docker-compose exec app sh
70
+
71
+ clean: ## Remove all containers, volumes, and images
72
+ docker-compose down -v --rmi all
73
+ @echo "Everything cleaned up!"
74
+
75
+ dev-watch: ## Start development with hot reload
76
+ @echo "Starting development server with hot reload..."
77
+ bun run dev
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # Echo
2
+
3
+ <div align="center">
4
+
5
+ ![Echo Logo](public/logo-64.svg)
6
+
7
+ **Open-source product feedback platform for modern teams.**
8
+
9
+ Collect feedback, spot patterns, and ship what matters.
10
+
11
+ [Live Demo](https://https://echo-khaki-eta.vercel.app/) - [Feedback](https://github.com/nexttylabs/echo/issues)
12
+
13
+ [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html)
14
+ [![GitHub stars](https://img.shields.io/github/stars/nexttylabs/echo.svg)](https://github.com/nexttylabs/echo/stargazers)
15
+ [![GitHub forks](https://img.shields.io/github/forks/nexttylabs/echo.svg)](https://github.com/nexttylabs/echo/network)
16
+ [![GitHub issues](https://img.shields.io/github/issues/nexttylabs/echo.svg)](https://github.com/nexttylabs/echo/issues)
17
+
18
+ </div>
19
+
20
+ ## Why Echo
21
+
22
+ Echo helps product teams centralize user feedback, identify themes, and make confident roadmap decisions without losing control of data or workflow.
23
+
24
+ ## Quickstart
25
+
26
+ ### Docker (recommended)
27
+
28
+ ```bash
29
+ # Clone the repository
30
+ git clone https://github.com/nexttylabs/echo.git
31
+ cd echo
32
+
33
+ # Copy environment variables
34
+ cp .env.example .env
35
+
36
+ # Start services
37
+ docker-compose up -d
38
+ ```
39
+
40
+ Visit http://localhost:3000
41
+
42
+ ### Local development
43
+
44
+ ```bash
45
+ # Install dependencies
46
+ bun install
47
+
48
+ # Copy environment variables
49
+ cp .env.example .env.local
50
+
51
+ # Run database migrations
52
+ bun run db:migrate
53
+
54
+ # Start development server
55
+ bun dev
56
+ ```
57
+
58
+ Visit http://localhost:3000
59
+
60
+ ## Core workflow
61
+
62
+ 1. Create a project and customize your public feedback portal.
63
+ 2. Collect feedback (embedded portal, admin entry, or API).
64
+ 3. Triage, prioritize, and share progress with your users.
65
+
66
+ ## Features
67
+
68
+ - **Feedback collection**: embedded portal, voting, attachments
69
+ - **Management workflow**: statuses, filters, and prioritization
70
+ - **AI assist**: basic auto-classification and duplicate hints
71
+ - **Team collaboration**: invitations and role-based access control
72
+ - **Self-hosting**: run on your infrastructure with Docker
73
+ - **API-ready**: integrate with existing product workflows
74
+
75
+ ## Use cases
76
+
77
+ - **Product teams**: prioritize roadmap decisions with data
78
+ - **Support teams**: capture customer feedback and context
79
+ - **Engineering teams**: track requests from idea to shipping
80
+
81
+ ## Tech stack
82
+
83
+ - **Frontend**: Next.js 16 + React 19 + TypeScript
84
+ - **UI**: Shadcn/ui + Tailwind CSS v4
85
+ - **Backend**: Next.js API Routes
86
+ - **Database**: PostgreSQL + Drizzle ORM
87
+ - **Deployment**: Docker + Docker Compose
88
+ - **Testing**: Playwright (E2E) + Vitest (Unit)
89
+
90
+ ## Project structure
91
+
92
+ ```
93
+ echo/
94
+ |-- app/ # Next.js App Router
95
+ | |-- (auth)/ # Authentication pages
96
+ | |-- (dashboard)/ # Dashboard pages
97
+ | |-- api/ # API routes
98
+ | `-- portal/ # Public feedback portal
99
+ |-- components/ # React components
100
+ | |-- ui/ # Shadcn/ui base components
101
+ | |-- forms/ # Form components
102
+ | `-- feedback/ # Feedback-related components
103
+ |-- lib/ # Utility functions and config
104
+ |-- db/ # Database schema and migrations
105
+ |-- public/ # Static assets
106
+ `-- docs/ # Project documentation
107
+ ```
108
+
109
+ ## Configuration
110
+
111
+ Create a `.env` file and set the following values:
112
+
113
+ ```env
114
+ # Database
115
+ DATABASE_URL="postgresql://user:password@localhost:5432/echo"
116
+
117
+ # Authentication
118
+ BETTER_AUTH_SECRET="your-secret-key"
119
+ BETTER_AUTH_URL="http://localhost:3000"
120
+
121
+ # Email (optional)
122
+ SMTP_HOST="smtp.gmail.com"
123
+ SMTP_PORT=587
124
+ SMTP_USER="your-email@gmail.com"
125
+ SMTP_PASS="your-app-password"
126
+
127
+ # AI (optional)
128
+ OPENAI_API_KEY="your-openai-api-key"
129
+ ```
130
+
131
+ ## Database
132
+
133
+ ```bash
134
+ # Generate migration files
135
+ bun run db:generate
136
+
137
+ # Run migrations
138
+ bun run db:migrate
139
+
140
+ # Reset database (development environment)
141
+ bun run db:reset
142
+ ```
143
+
144
+ ## Testing
145
+
146
+ ```bash
147
+ # Run unit tests
148
+ bun test
149
+
150
+ # Run E2E tests
151
+ bun run test:e2e
152
+
153
+ # Run tests with coverage
154
+ bun run test:coverage
155
+ ```
156
+
157
+ ## Get involved
158
+
159
+ - Join the conversation in our [Discord](https://discord.gg/echo)
160
+ - Review or open [issues](https://github.com/nexttylabs/echo/issues)
161
+ - Contribute via [pull requests](https://github.com/nexttylabs/echo/pulls)
162
+
163
+ ## Contributing
164
+
165
+ We welcome contributions from the community.
166
+
167
+ 1. Fork the project
168
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
169
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
170
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
171
+ 5. Open a Pull Request
172
+
173
+ ## Roadmap
174
+
175
+ - **v1.0 (current)**: feedback collection, auth, Docker, basic AI classification
176
+ - **v1.1 (planned)**: comments, email notifications, public roadmap
177
+ - **v1.2 (future)**: advanced AI, white-labeling, SSO, mobile
178
+
179
+ ## License
180
+
181
+ This project is licensed under the [GNU AGPL v3](LICENSE).
182
+
183
+ ## Acknowledgments
184
+
185
+ - [Next.js](https://nextjs.org/) - React framework
186
+ - [Shadcn/ui](https://ui.shadcn.com/) - UI components
187
+ - [Drizzle ORM](https://orm.drizzle.team/) - TypeScript ORM
188
+ - [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS
189
+
190
+ ---
191
+
192
+ <div align="center">
193
+
194
+ **[Star the repo](https://github.com/nexttylabs/echo) if Echo helps your team.**
195
+
196
+ Made by the nexttylabs Team
197
+
198
+ </div>
@@ -0,0 +1,53 @@
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 { headers } from "next/headers";
19
+ import { redirect } from "next/navigation";
20
+ import { getTranslations } from "next-intl/server";
21
+ import { LoginForm } from "@/components/auth/login-form";
22
+ import { auth } from "@/lib/auth/config";
23
+
24
+ export default async function LoginPage() {
25
+ let session = null;
26
+ try {
27
+ session = await auth.api.getSession({ headers: await headers() });
28
+ } catch {
29
+ // Invalid session cookie, continue to show login form
30
+ }
31
+
32
+ if (session) {
33
+ redirect("/dashboard");
34
+ }
35
+
36
+ const t = await getTranslations("auth.login");
37
+
38
+ return (
39
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 px-4 py-12">
40
+ <div className="mx-auto flex w-full max-w-md flex-col gap-6">
41
+ <div className="text-center">
42
+ <h1 className="text-3xl font-semibold tracking-tight text-slate-900">
43
+ {t("pageTitle")}
44
+ </h1>
45
+ <p className="mt-2 text-sm text-slate-600">
46
+ {t("pageSubtitle")}
47
+ </p>
48
+ </div>
49
+ <LoginForm />
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,48 @@
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 { headers } from "next/headers";
19
+ import { redirect } from "next/navigation";
20
+ import { getTranslations } from "next-intl/server";
21
+ import { RegisterForm } from "@/components/auth/register-form";
22
+ import { auth } from "@/lib/auth/config";
23
+
24
+ export default async function RegisterPage() {
25
+ const session = await auth.api.getSession({ headers: await headers() });
26
+
27
+ if (session) {
28
+ redirect("/dashboard");
29
+ }
30
+
31
+ const t = await getTranslations("auth.register");
32
+
33
+ return (
34
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 px-4 py-12">
35
+ <div className="mx-auto flex w-full max-w-md flex-col gap-6">
36
+ <div className="text-center">
37
+ <h1 className="text-3xl font-semibold tracking-tight text-slate-900">
38
+ {t("pageTitle")}
39
+ </h1>
40
+ <p className="mt-2 text-sm text-slate-600">
41
+ {t("pageSubtitle")}
42
+ </p>
43
+ </div>
44
+ <RegisterForm />
45
+ </div>
46
+ </div>
47
+ );
48
+ }
@@ -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
+ import { redirect } from "next/navigation";
19
+
20
+ export default function SignInRedirectPage() {
21
+ redirect("/login");
22
+ }
@@ -0,0 +1,103 @@
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 { cookies, headers } from "next/headers";
19
+ import { notFound, redirect } from "next/navigation";
20
+ import { and, eq } from "drizzle-orm";
21
+ import { auth } from "@/lib/auth/config";
22
+ import { db } from "@/lib/db";
23
+ import { feedback } from "@/lib/db/schema";
24
+ import { FeedbackEditForm } from "@/components/feedback/feedback-edit-form";
25
+ import { canEditFeedback, type UserRole } from "@/lib/auth/permissions";
26
+ import { getOrgContext } from "@/lib/auth/org-context";
27
+ import { getRequestUrl } from "@/lib/http/get-request-url";
28
+
29
+ interface PageProps {
30
+ params: Promise<{ id: string }>;
31
+ }
32
+
33
+ export default async function FeedbackEditPage({ params }: PageProps) {
34
+ const headerList = await headers();
35
+ const cookieStore = await cookies();
36
+ const session = await auth.api.getSession({ headers: headerList });
37
+
38
+ if (!session?.user) {
39
+ redirect("/login");
40
+ }
41
+
42
+ const userRole = (session.user as { role?: string }).role as UserRole | undefined;
43
+ if (!userRole || !canEditFeedback(userRole)) {
44
+ redirect("/admin/feedback");
45
+ }
46
+
47
+ const { id } = await params;
48
+ const feedbackId = parseInt(id);
49
+
50
+ if (isNaN(feedbackId)) {
51
+ notFound();
52
+ }
53
+
54
+ if (!db) {
55
+ throw new Error("Database not configured");
56
+ }
57
+
58
+ let organizationId: string | null = null;
59
+ try {
60
+ const url = getRequestUrl(
61
+ headerList,
62
+ `/admin/feedback/${feedbackId}/edit`,
63
+ );
64
+ const context = await getOrgContext({
65
+ request: { nextUrl: url, headers: headerList, cookies: cookieStore },
66
+ db,
67
+ userId: session.user.id,
68
+ requireMembership: true,
69
+ });
70
+ organizationId = context.organizationId;
71
+ } catch {
72
+ notFound();
73
+ }
74
+
75
+ const [row] = await db
76
+ .select({
77
+ title: feedback.title,
78
+ description: feedback.description,
79
+ deletedAt: feedback.deletedAt,
80
+ })
81
+ .from(feedback)
82
+ .where(
83
+ and(
84
+ eq(feedback.feedbackId, feedbackId),
85
+ eq(feedback.organizationId, organizationId ?? ""),
86
+ ),
87
+ )
88
+ .limit(1);
89
+
90
+ if (!row || row.deletedAt !== null) {
91
+ notFound();
92
+ }
93
+
94
+ return (
95
+ <div className="container mx-auto py-8 px-4">
96
+ <FeedbackEditForm
97
+ feedbackId={feedbackId}
98
+ initialTitle={row.title}
99
+ initialDescription={row.description}
100
+ />
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,154 @@
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 { cookies, headers } from "next/headers";
19
+ import { notFound, redirect } from "next/navigation";
20
+ import { auth } from "@/lib/auth/config";
21
+ import { db } from "@/lib/db";
22
+ import { getFeedbackById } from "@/lib/feedback/get-feedback-by-id";
23
+ import { FeedbackDetailView } from "@/components/feedback/feedback-detail-view";
24
+ import { InternalNotes } from "@/components/comment/internal-notes";
25
+ import { PublicComments } from "@/components/comment/public-comments";
26
+ import { canDeleteFeedback, canEditFeedback, canUpdateFeedbackStatus, type UserRole } from "@/lib/auth/permissions";
27
+ import { getOrgContext } from "@/lib/auth/org-context";
28
+ import { getRequestUrl } from "@/lib/http/get-request-url";
29
+
30
+ interface PageProps {
31
+ params: Promise<{ id: string }>;
32
+ }
33
+
34
+ export default async function FeedbackDetailPage({ params }: PageProps) {
35
+ const headerList = await headers();
36
+ const cookieStore = await cookies();
37
+ const session = await auth.api.getSession({ headers: headerList });
38
+
39
+ if (!session?.user) {
40
+ redirect("/login");
41
+ }
42
+
43
+ const { id } = await params;
44
+ const feedbackId = parseInt(id);
45
+
46
+ if (isNaN(feedbackId)) {
47
+ notFound();
48
+ }
49
+
50
+ if (!db) {
51
+ throw new Error("Database not configured");
52
+ }
53
+
54
+ let memberRole: UserRole | null = null;
55
+ let organizationId: string | null = null;
56
+ try {
57
+ const url = getRequestUrl(headerList, `/admin/feedback/${feedbackId}`);
58
+ const context = await getOrgContext({
59
+ request: { nextUrl: url, headers: headerList, cookies: cookieStore },
60
+ db,
61
+ userId: session.user.id,
62
+ requireMembership: true,
63
+ });
64
+ memberRole = (context.memberRole as UserRole) ?? null;
65
+ organizationId = context.organizationId;
66
+ } catch {
67
+ notFound();
68
+ }
69
+
70
+ const result = await getFeedbackById(db, feedbackId, {
71
+ userId: session.user.id,
72
+ });
73
+
74
+ if (!result || "deleted" in result) {
75
+ notFound();
76
+ }
77
+
78
+ if (!organizationId || result.feedback.organizationId !== organizationId) {
79
+ notFound();
80
+ }
81
+
82
+ const canEdit = !!memberRole && canEditFeedback(memberRole);
83
+ const canDelete = !!memberRole && canDeleteFeedback(memberRole);
84
+ const canUpdateStatus = !!memberRole && canUpdateFeedbackStatus(memberRole);
85
+
86
+ return (
87
+ <div className="container mx-auto py-8 px-4">
88
+ <FeedbackDetailView
89
+ feedback={{
90
+ feedbackId: result.feedback.feedbackId,
91
+ title: result.feedback.title,
92
+ description: result.feedback.description,
93
+ type: result.feedback.type,
94
+ priority: result.feedback.priority,
95
+ status: result.feedback.status,
96
+ createdAt: result.feedback.createdAt.toISOString(),
97
+ updatedAt: result.feedback.updatedAt.toISOString(),
98
+ submittedOnBehalf: result.feedback.submittedOnBehalf,
99
+ customerInfo: result.feedback.customerInfo as {
100
+ name: string;
101
+ email: string;
102
+ phone?: string;
103
+ } | null,
104
+ attachments: result.attachments.map((a) => ({
105
+ attachmentId: a.attachmentId,
106
+ fileName: a.fileName,
107
+ filePath: a.filePath,
108
+ fileSize: a.fileSize,
109
+ mimeType: a.mimeType,
110
+ createdAt: a.createdAt.toISOString(),
111
+ })),
112
+ votes: {
113
+ count: result.votes.count,
114
+ list: result.votes.list.map((v) => ({
115
+ voteId: v.voteId,
116
+ visitorId: v.visitorId,
117
+ userId: v.userId,
118
+ userName: v.userName,
119
+ userEmail: v.userEmail,
120
+ createdAt: v.createdAt.toISOString(),
121
+ })),
122
+ userVote: result.votes.userVote,
123
+ },
124
+ statusHistory: result.statusHistory.map((h) => ({
125
+ historyId: h.historyId,
126
+ oldStatus: h.oldStatus,
127
+ newStatus: h.newStatus,
128
+ changedBy: h.changedBy,
129
+ changedAt: h.changedAt.toISOString(),
130
+ comment: h.comment,
131
+ })),
132
+ }}
133
+ canEdit={canEdit}
134
+ canDelete={canDelete}
135
+ canUpdateStatus={canUpdateStatus}
136
+ >
137
+ {/* Comments section in main content area */}
138
+ <PublicComments
139
+ feedbackId={feedbackId}
140
+ isAuthenticated={true}
141
+ organizationId={organizationId}
142
+ />
143
+
144
+ {memberRole && (
145
+ <InternalNotes
146
+ feedbackId={feedbackId}
147
+ canDelete={memberRole === "admin"}
148
+ currentUserId={session.user.id}
149
+ />
150
+ )}
151
+ </FeedbackDetailView>
152
+ </div>
153
+ );
154
+ }