@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.
- package/.changeset/README.md +21 -0
- package/.changeset/config.json +11 -0
- package/.changeset/cozy-ghosts-care.md +5 -0
- package/.changeset/sharp-lines-stand.md +5 -0
- package/.changeset/sour-doodles-eat.md +5 -0
- package/.changeset/tender-moose-shop.md +5 -0
- package/.github/pull_request_template.md +13 -0
- package/.github/workflows/ci.yml +41 -0
- package/.github/workflows/publish.yml +44 -0
- package/.github/workflows/release.yml +73 -0
- package/AGENTS.md +92 -0
- package/CHANGELOG.md +13 -0
- package/Dockerfile +57 -0
- package/LICENSE +661 -0
- package/Makefile +77 -0
- package/README.md +198 -0
- package/app/(auth)/login/page.tsx +53 -0
- package/app/(auth)/register/page.tsx +48 -0
- package/app/(auth)/sign-in/page.tsx +22 -0
- package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +103 -0
- package/app/(dashboard)/admin/feedback/[id]/page.tsx +154 -0
- package/app/(dashboard)/admin/feedback/new/page.tsx +91 -0
- package/app/(dashboard)/admin/feedback/page.tsx +81 -0
- package/app/(dashboard)/admin/layout.tsx +48 -0
- package/app/(dashboard)/analytics/portal/page.tsx +30 -0
- package/app/(dashboard)/dashboard/page.tsx +133 -0
- package/app/(dashboard)/layout.tsx +69 -0
- package/app/(dashboard)/no-access/page.tsx +45 -0
- package/app/(dashboard)/settings/access/page.tsx +56 -0
- package/app/(dashboard)/settings/api-keys/page.tsx +55 -0
- package/app/(dashboard)/settings/appearance/page.tsx +40 -0
- package/app/(dashboard)/settings/branding/page.tsx +62 -0
- package/app/(dashboard)/settings/changelog/page.tsx +51 -0
- package/app/(dashboard)/settings/danger-zone/page.tsx +92 -0
- package/app/(dashboard)/settings/feedback/page.tsx +63 -0
- package/app/(dashboard)/settings/integrations/page.tsx +94 -0
- package/app/(dashboard)/settings/layout.tsx +43 -0
- package/app/(dashboard)/settings/modules/page.tsx +54 -0
- package/app/(dashboard)/settings/notifications/page.tsx +48 -0
- package/app/(dashboard)/settings/organization/page.tsx +104 -0
- package/app/(dashboard)/settings/organization/portal/access/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/experience/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/growth/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/layout.tsx +24 -0
- package/app/(dashboard)/settings/organization/portal/page.tsx +22 -0
- package/app/(dashboard)/settings/organizations/[orgId]/members/page.tsx +69 -0
- package/app/(dashboard)/settings/organizations/new/page.tsx +36 -0
- package/app/(dashboard)/settings/page.tsx +22 -0
- package/app/(dashboard)/settings/portal-access/page.tsx +53 -0
- package/app/(dashboard)/settings/portal-branding/page.tsx +59 -0
- package/app/(dashboard)/settings/portal-growth/page.tsx +57 -0
- package/app/(dashboard)/settings/portal-modules/page.tsx +49 -0
- package/app/(dashboard)/settings/portal-resources/page.tsx +66 -0
- package/app/(dashboard)/settings/profile/page.tsx +48 -0
- package/app/(dashboard)/settings/widgets/page.tsx +63 -0
- package/app/(public)/[organizationSlug]/changelog/page.tsx +109 -0
- package/app/(public)/[organizationSlug]/feedback/[id]/page.tsx +146 -0
- package/app/(public)/[organizationSlug]/page.tsx +160 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +142 -0
- package/app/(public)/docs/page.tsx +48 -0
- package/app/(public)/feedback/[id]/not-found.tsx +33 -0
- package/app/(public)/feedback/[id]/page.tsx +102 -0
- package/app/(public)/invite/[token]/page.tsx +121 -0
- package/app/(public)/page.tsx +22 -0
- package/app/(public)/widget/[organizationId]/page.tsx +122 -0
- package/app/api/_utils.ts +29 -0
- package/app/api/admin/backup/route.ts +72 -0
- package/app/api/api-keys/[keyId]/route.ts +92 -0
- package/app/api/api-keys/route.ts +116 -0
- package/app/api/auth/[...all]/route.ts +21 -0
- package/app/api/auth/clear-session/route.ts +43 -0
- package/app/api/auth/register/handler.ts +176 -0
- package/app/api/auth/register/route.ts +26 -0
- package/app/api/docs/route.ts +28 -0
- package/app/api/feedback/[id]/comments/[commentId]/route.ts +105 -0
- package/app/api/feedback/[id]/comments/route.ts +421 -0
- package/app/api/feedback/[id]/duplicates/route.ts +285 -0
- package/app/api/feedback/[id]/handler.ts +91 -0
- package/app/api/feedback/[id]/processing-status/route.ts +199 -0
- package/app/api/feedback/[id]/reclassify/route.ts +145 -0
- package/app/api/feedback/[id]/route.ts +511 -0
- package/app/api/feedback/[id]/suggest-tags/route.ts +227 -0
- package/app/api/feedback/[id]/sync-github/route.ts +52 -0
- package/app/api/feedback/[id]/vote/route.ts +431 -0
- package/app/api/feedback/bulk/route.ts +212 -0
- package/app/api/feedback/handler.ts +138 -0
- package/app/api/feedback/route.ts +298 -0
- package/app/api/feedback/similar/route.ts +100 -0
- package/app/api/health/route.test.ts +64 -0
- package/app/api/health/route.ts +92 -0
- package/app/api/identify/jwt/route.ts +29 -0
- package/app/api/integrations/github/route.ts +196 -0
- package/app/api/internal/domain-lookup/route.ts +67 -0
- package/app/api/invitations/accept/handler.ts +101 -0
- package/app/api/invitations/accept/route.ts +29 -0
- package/app/api/notifications/preferences/route.ts +109 -0
- package/app/api/organizations/[orgId]/handler.ts +123 -0
- package/app/api/organizations/[orgId]/invitations/handler.ts +121 -0
- package/app/api/organizations/[orgId]/invitations/route.ts +29 -0
- package/app/api/organizations/[orgId]/members/[memberId]/handler.ts +208 -0
- package/app/api/organizations/[orgId]/members/[memberId]/route.ts +30 -0
- package/app/api/organizations/[orgId]/members/handler.ts +77 -0
- package/app/api/organizations/[orgId]/members/route.ts +29 -0
- package/app/api/organizations/[orgId]/route.ts +30 -0
- package/app/api/organizations/handler.ts +97 -0
- package/app/api/organizations/route.ts +29 -0
- package/app/api/tags/sync/route.ts +88 -0
- package/app/api/upload/handler.ts +79 -0
- package/app/api/upload/route.ts +37 -0
- package/app/api/v1/feedback/[id]/route.ts +276 -0
- package/app/api/v1/feedback/route.ts +250 -0
- package/app/api/v1/spec/route.ts +356 -0
- package/app/api/webhooks/[webhookId]/route.ts +213 -0
- package/app/api/webhooks/github/route.ts +158 -0
- package/app/api/webhooks/route.ts +143 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +139 -0
- package/app/health/route.ts +108 -0
- package/app/layout.tsx +60 -0
- package/bun.lock +2503 -0
- package/components/api/rate-limit-info.tsx +86 -0
- package/components/api-keys/api-key-manager.tsx +262 -0
- package/components/auth/login-form.tsx +207 -0
- package/components/auth/register-form.tsx +230 -0
- package/components/comment/comment-form.tsx +111 -0
- package/components/comment/internal-notes.tsx +219 -0
- package/components/comment/public-comments.tsx +387 -0
- package/components/component-example-client-only.tsx +29 -0
- package/components/component-example.tsx +519 -0
- package/components/dashboard/index.ts +22 -0
- package/components/dashboard/organization-switcher.tsx +96 -0
- package/components/dashboard/quick-actions.tsx +57 -0
- package/components/dashboard/recent-feedback-list.tsx +152 -0
- package/components/dashboard/stats-cards.tsx +88 -0
- package/components/dashboard/status-chart.tsx +106 -0
- package/components/example.tsx +70 -0
- package/components/feedback/attachment-list.tsx +103 -0
- package/components/feedback/auto-classification-badge.tsx +92 -0
- package/components/feedback/classification-override.tsx +64 -0
- package/components/feedback/duplicate-suggestions-inline.tsx +158 -0
- package/components/feedback/duplicate-suggestions.tsx +188 -0
- package/components/feedback/embedded-feedback-form.tsx +439 -0
- package/components/feedback/feedback-actions.tsx +160 -0
- package/components/feedback/feedback-bulk-actions.tsx +184 -0
- package/components/feedback/feedback-detail-view.tsx +321 -0
- package/components/feedback/feedback-detail.tsx +305 -0
- package/components/feedback/feedback-edit-form.tsx +131 -0
- package/components/feedback/feedback-filters.tsx +222 -0
- package/components/feedback/feedback-list-controls.tsx +433 -0
- package/components/feedback/feedback-list-item.tsx +298 -0
- package/components/feedback/feedback-list-skeleton.tsx +49 -0
- package/components/feedback/feedback-list.tsx +523 -0
- package/components/feedback/feedback-sorter.tsx +117 -0
- package/components/feedback/feedback-stats.tsx +124 -0
- package/components/feedback/file-upload.tsx +289 -0
- package/components/feedback/processing-status.tsx +161 -0
- package/components/feedback/status-history.tsx +134 -0
- package/components/feedback/status-selector.tsx +153 -0
- package/components/feedback/submit-on-behalf-form.tsx +403 -0
- package/components/feedback/tag-suggestions.tsx +212 -0
- package/components/feedback/vote-button.tsx +113 -0
- package/components/feedback/vote-list.tsx +108 -0
- package/components/integrations/github-config.tsx +200 -0
- package/components/landing/hero.tsx +150 -0
- package/components/layout/dashboard-layout.tsx +59 -0
- package/components/layout/index.ts +20 -0
- package/components/layout/language-switcher.tsx +129 -0
- package/components/layout/mobile-sidebar.tsx +66 -0
- package/components/layout/sidebar.tsx +279 -0
- package/components/portal/changelog-entry.tsx +132 -0
- package/components/portal/changelog-list.tsx +85 -0
- package/components/portal/contributor-badge.tsx +29 -0
- package/components/portal/contributors-sidebar.tsx +98 -0
- package/components/portal/create-post-dialog.tsx +247 -0
- package/components/portal/feedback-board.tsx +205 -0
- package/components/portal/feedback-post-card.tsx +198 -0
- package/components/portal/help-center.tsx +169 -0
- package/components/portal/leaderboard.tsx +29 -0
- package/components/portal/portal-header.tsx +153 -0
- package/components/portal/portal-layout.tsx +62 -0
- package/components/portal/portal-modules-panel.tsx +118 -0
- package/components/portal/portal-nav.tsx +59 -0
- package/components/portal/portal-overview.tsx +174 -0
- package/components/portal/portal-settings-nav.tsx +62 -0
- package/components/portal/portal-settings-shell.tsx +71 -0
- package/components/portal/portal-shell.tsx +62 -0
- package/components/portal/portal-tab-nav.tsx +77 -0
- package/components/portal/project-switcher.tsx +20 -0
- package/components/portal/roadmap-board.tsx +82 -0
- package/components/portal/roadmap-card.tsx +76 -0
- package/components/portal/roadmap-column.tsx +78 -0
- package/components/portal/settings-forms/access-form.tsx +194 -0
- package/components/portal/settings-forms/copy-form.tsx +95 -0
- package/components/portal/settings-forms/index.ts +23 -0
- package/components/portal/settings-forms/languages-form.tsx +223 -0
- package/components/portal/settings-forms/seo-form.tsx +156 -0
- package/components/portal/settings-forms/sharing-form.tsx +155 -0
- package/components/portal/settings-forms/theme-form.tsx +104 -0
- package/components/settings/api-keys-list.tsx +167 -0
- package/components/settings/appearance-form.tsx +71 -0
- package/components/settings/index.ts +25 -0
- package/components/settings/invite-member-form.tsx +119 -0
- package/components/settings/notification-preferences.tsx +174 -0
- package/components/settings/organization-form.tsx +165 -0
- package/components/settings/organization-members-list.tsx +197 -0
- package/components/settings/profile-form.tsx +124 -0
- package/components/settings/role-selector.tsx +57 -0
- package/components/settings/settings-sidebar.tsx +115 -0
- package/components/shared/pagination.tsx +215 -0
- package/components/ui/alert-dialog.tsx +201 -0
- package/components/ui/alert.tsx +75 -0
- package/components/ui/avatar.tsx +126 -0
- package/components/ui/badge.tsx +62 -0
- package/components/ui/button.tsx +77 -0
- package/components/ui/card.tsx +111 -0
- package/components/ui/combobox.tsx +311 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +272 -0
- package/components/ui/field.tsx +256 -0
- package/components/ui/input-group.tsx +164 -0
- package/components/ui/input.tsx +36 -0
- package/components/ui/label.tsx +41 -0
- package/components/ui/pagination.tsx +142 -0
- package/components/ui/select.tsx +202 -0
- package/components/ui/separator.tsx +45 -0
- package/components/ui/sheet.tsx +151 -0
- package/components/ui/skeleton.tsx +32 -0
- package/components/ui/switch.tsx +49 -0
- package/components/ui/table.tsx +118 -0
- package/components/ui/tabs.tsx +107 -0
- package/components/ui/textarea.tsx +35 -0
- package/components/ui/tooltip.tsx +78 -0
- package/components/widget/widget-form.tsx +439 -0
- package/components.json +24 -0
- package/db/init/01-init.sql +13 -0
- package/docker-compose.dev.yml +26 -0
- package/docker-compose.yml +98 -0
- package/docs/architecture.md +259 -0
- package/docs/component-inventory.md +261 -0
- package/docs/database-migrations.md +76 -0
- package/docs/development-guide.md +209 -0
- package/docs/e2e-user-flows.csv +31 -0
- package/docs/er-diagram-feedback.mmd +138 -0
- package/docs/er-diagram.mmd +281 -0
- package/docs/i18n-check-report.md +296 -0
- package/docs/index.md +214 -0
- package/docs/logic-chain.md +94 -0
- package/docs/plans/2026-01-02-database-migration-scripts.md +496 -0
- package/docs/plans/2026-01-02-user-login-design.md +37 -0
- package/docs/plans/2026-01-02-user-login.md +437 -0
- package/docs/plans/2026-01-02-user-registration-design.md +47 -0
- package/docs/plans/2026-01-02-user-registration.md +628 -0
- package/docs/plans/2026-01-03-roles-permissions-design.md +20 -0
- package/docs/plans/2026-01-03-roles-permissions.md +266 -0
- package/docs/plans/2026-01-05-authentication-middleware.md +207 -0
- package/docs/plans/2026-01-05-member-removal.md +186 -0
- package/docs/plans/2026-01-05-organization-creation.md +374 -0
- package/docs/plans/2026-01-05-rbac-middleware.md +112 -0
- package/docs/plans/2026-01-05-role-configuration.md +441 -0
- package/docs/plans/2026-01-06-file-upload-support.md +804 -0
- package/docs/plans/2026-01-06-permission-check-hook.md +155 -0
- package/docs/plans/2026-01-06-resource-ownership-check.md +231 -0
- package/docs/plans/2026-01-07-feedback-tracking-link.md +459 -0
- package/docs/plans/2026-01-09-logout-redirect-design.md +52 -0
- package/docs/plans/2026-01-09-phase2-3-plan.md +654 -0
- package/docs/plans/2026-01-09-portal-execution-plan.md +408 -0
- package/docs/plans/2026-01-09-project-delete-feature-design.md +163 -0
- package/docs/plans/2026-01-09-project-delete-implementation.md +451 -0
- package/docs/plans/2026-01-09-project-edit-delete-design.md +52 -0
- package/docs/plans/2026-01-09-settings-center-design.md +114 -0
- package/docs/plans/2026-01-09-settings-center.md +948 -0
- package/docs/plans/2026-01-10-organization-only-design.md +66 -0
- package/docs/plans/2026-01-10-organization-only-implementation.md +433 -0
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +18 -0
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +296 -0
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +173 -0
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +82 -0
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +521 -0
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +75 -0
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +293 -0
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +180 -0
- package/docs/plans/2026-01-16-e2e-test-fixes.md +158 -0
- package/docs/plans/2026-01-17-admin-feedback-filters.md +214 -0
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +453 -0
- package/docs/plans/2026-01-18-changesets-design.md +40 -0
- package/docs/product_changes.md +37 -0
- package/docs/project-overview.md +159 -0
- package/docs/project-scan-report.json +104 -0
- package/docs/route-role-visibility.md +51 -0
- package/docs/source-tree-analysis.md +150 -0
- package/docs/testing/delete-project-manual-tests.md +18 -0
- package/docs/user-story-tracking.md +191 -0
- package/drizzle.config.ts +32 -0
- package/eslint.config.mjs +19 -0
- package/hooks/use-permissions.ts +56 -0
- package/i18n/config.ts +45 -0
- package/i18n/request.ts +28 -0
- package/i18n/resolve-locale.ts +38 -0
- package/lib/api/errors.ts +62 -0
- package/lib/auth/cli-config.ts +35 -0
- package/lib/auth/client.ts +20 -0
- package/lib/auth/config.ts +55 -0
- package/lib/auth/jwt-identity.ts +21 -0
- package/lib/auth/org-context.ts +71 -0
- package/lib/auth/organization.ts +107 -0
- package/lib/auth/permissions.ts +87 -0
- package/lib/auth/session.ts +23 -0
- package/lib/config/rate-limits.ts +64 -0
- package/lib/dashboard/get-dashboard-stats.ts +136 -0
- package/lib/db/index.ts +41 -0
- package/lib/db/migrate.test.ts +49 -0
- package/lib/db/migrate.ts +62 -0
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +53 -0
- package/lib/db/migrations/0001_wandering_sunfire.sql +27 -0
- package/lib/db/migrations/0002_shallow_speedball.sql +1 -0
- package/lib/db/migrations/0003_add_org_description.sql +1 -0
- package/lib/db/migrations/0003_boring_wild_pack.sql +13 -0
- package/lib/db/migrations/0004_windy_tyrannus.sql +27 -0
- package/lib/db/migrations/0005_perpetual_doorman.sql +5 -0
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +13 -0
- package/lib/db/migrations/0007_clever_captain_cross.sql +14 -0
- package/lib/db/migrations/0008_sparkling_pandemic.sql +2 -0
- package/lib/db/migrations/0009_happy_black_tom.sql +29 -0
- package/lib/db/migrations/0010_kind_junta.sql +8 -0
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +25 -0
- package/lib/db/migrations/0012_giant_power_man.sql +24 -0
- package/lib/db/migrations/0013_damp_titanium_man.sql +17 -0
- package/lib/db/migrations/0014_blue_alice.sql +18 -0
- package/lib/db/migrations/0015_webhook_tables.sql +41 -0
- package/lib/db/migrations/0016_github_integration.sql +30 -0
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +22 -0
- package/lib/db/migrations/0017_slimy_inhumans.sql +6 -0
- package/lib/db/migrations/0018_same_spitfire.sql +1 -0
- package/lib/db/migrations/0019_jittery_loners.sql +16 -0
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +14 -0
- package/lib/db/migrations/meta/0000_snapshot.json +374 -0
- package/lib/db/migrations/meta/0001_snapshot.json +553 -0
- package/lib/db/migrations/meta/0002_snapshot.json +560 -0
- package/lib/db/migrations/meta/0003_snapshot.json +650 -0
- package/lib/db/migrations/meta/0004_snapshot.json +852 -0
- package/lib/db/migrations/meta/0005_snapshot.json +900 -0
- package/lib/db/migrations/meta/0006_snapshot.json +1011 -0
- package/lib/db/migrations/meta/0007_snapshot.json +1125 -0
- package/lib/db/migrations/meta/0008_snapshot.json +1146 -0
- package/lib/db/migrations/meta/0009_snapshot.json +1386 -0
- package/lib/db/migrations/meta/0010_snapshot.json +1419 -0
- package/lib/db/migrations/meta/0011_snapshot.json +1615 -0
- package/lib/db/migrations/meta/0012_snapshot.json +1805 -0
- package/lib/db/migrations/meta/0013_snapshot.json +1948 -0
- package/lib/db/migrations/meta/0014_snapshot.json +2082 -0
- package/lib/db/migrations/meta/0015_snapshot.json +2476 -0
- package/lib/db/migrations/meta/0016_snapshot.json +2633 -0
- package/lib/db/migrations/meta/0017_snapshot.json +2680 -0
- package/lib/db/migrations/meta/0018_snapshot.json +2686 -0
- package/lib/db/migrations/meta/0019_snapshot.json +2741 -0
- package/lib/db/migrations/meta/_journal.json +146 -0
- package/lib/db/schema/ai-processing.ts +90 -0
- package/lib/db/schema/api-keys.ts +61 -0
- package/lib/db/schema/attachments.ts +48 -0
- package/lib/db/schema/auth.ts +111 -0
- package/lib/db/schema/comments.ts +74 -0
- package/lib/db/schema/duplicates.ts +80 -0
- package/lib/db/schema/feedback.ts +88 -0
- package/lib/db/schema/github-integrations.ts +66 -0
- package/lib/db/schema/index.ts +35 -0
- package/lib/db/schema/invitations.ts +32 -0
- package/lib/db/schema/notifications.ts +85 -0
- package/lib/db/schema/organization-members.ts +37 -0
- package/lib/db/schema/organization-settings.ts +134 -0
- package/lib/db/schema/organizations.ts +30 -0
- package/lib/db/schema/projects.ts +145 -0
- package/lib/db/schema/status-history.ts +63 -0
- package/lib/db/schema/tags.ts +194 -0
- package/lib/db/schema/user-profiles.ts +31 -0
- package/lib/db/schema/votes.ts +60 -0
- package/lib/db/schema/webhooks.ts +106 -0
- package/lib/feedback/filters.ts +28 -0
- package/lib/feedback/find-similar.ts +49 -0
- package/lib/feedback/get-feedback-by-id.ts +159 -0
- package/lib/feedback/prefill.ts +51 -0
- package/lib/http/get-request-url.ts +28 -0
- package/lib/integrations/github.ts +159 -0
- package/lib/invitations.ts +22 -0
- package/lib/logger.test.ts +31 -0
- package/lib/logger.ts +58 -0
- package/lib/middleware/api-key.ts +126 -0
- package/lib/middleware/rate-limit-keys.ts +47 -0
- package/lib/middleware/rate-limit.ts +148 -0
- package/lib/middleware/rbac.ts +39 -0
- package/lib/middleware/request-id.test.ts +28 -0
- package/lib/middleware/request-id.ts +30 -0
- package/lib/middleware/request-logger.test.ts +36 -0
- package/lib/middleware/request-logger.ts +41 -0
- package/lib/middleware/with-rate-limit.ts +33 -0
- package/lib/portal/analytics.ts +20 -0
- package/lib/portal/contributors.ts +27 -0
- package/lib/portal/i18n.ts +20 -0
- package/lib/portal/leaderboard-settings.ts +20 -0
- package/lib/portal/modules.ts +20 -0
- package/lib/portal/portal-copy.ts +20 -0
- package/lib/portal/public-context.tsx +110 -0
- package/lib/portal/seo.ts +20 -0
- package/lib/portal/settings-context.ts +56 -0
- package/lib/portal/sharing.ts +20 -0
- package/lib/portal/sorting.ts +20 -0
- package/lib/portal/theme.ts +20 -0
- package/lib/services/ai/classifier.ts +296 -0
- package/lib/services/ai/duplicate-detector.ts +255 -0
- package/lib/services/ai/tag-suggester.ts +108 -0
- package/lib/services/api-keys.ts +164 -0
- package/lib/services/backup.ts +173 -0
- package/lib/services/email/templates.ts +158 -0
- package/lib/services/email.ts +68 -0
- package/lib/services/github-sync.ts +205 -0
- package/lib/services/notifications/index.ts +224 -0
- package/lib/services/portal-settings.ts +157 -0
- package/lib/swagger/config.ts +296 -0
- package/lib/swagger/generate.ts +400 -0
- package/lib/upload/file-validator.ts +52 -0
- package/lib/upload/storage.ts +59 -0
- package/lib/utils/format.ts +26 -0
- package/lib/utils/slug.ts +28 -0
- package/lib/utils.ts +23 -0
- package/lib/validations/auth.ts +56 -0
- package/lib/validations/comment.ts +44 -0
- package/lib/validations/feedback.ts +51 -0
- package/lib/validations/invitations.ts +23 -0
- package/lib/validations/organizations.ts +34 -0
- package/lib/validations/projects.ts +49 -0
- package/lib/validators/feedback.ts +57 -0
- package/lib/validators/index.ts +18 -0
- package/lib/webhooks/events.ts +73 -0
- package/lib/webhooks/index.ts +21 -0
- package/lib/webhooks/retry.ts +188 -0
- package/lib/webhooks/sender.ts +183 -0
- package/lib/webhooks/verify.ts +37 -0
- package/lib/workers/feedback-processor.ts +255 -0
- package/messages/en.json +965 -0
- package/messages/jp.json +862 -0
- package/messages/zh-CN.json +855 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +66 -0
- package/package.json +84 -0
- package/playwright.config.ts +44 -0
- package/postcss.config.mjs +7 -0
- package/proxy.test.ts +131 -0
- package/proxy.ts +190 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo-64.svg +5 -0
- package/public/logo.svg +5 -0
- package/public/next.svg +1 -0
- package/public/openapi.json +673 -0
- package/public/uploads/.gitkeep +0 -0
- package/public/uploads/02695701-ded0-4c81-8a21-9326c1d65448.pdf +1 -0
- package/public/uploads/178843ea-2780-48ef-8988-f4cba442e4cb.pdf +1 -0
- package/public/uploads/24b0a9ef-da93-49da-934f-637f89c7871d.pdf +1 -0
- package/public/uploads/7a11626d-a8e4-4b91-a8eb-20b6213b0a5a.pdf +1 -0
- package/public/uploads/b0703f4d-6e7b-4aab-8191-1a7b15f1b8ee.pdf +1 -0
- package/public/uploads/c8de0aed-4d3a-44aa-83bb-6594b7a2ddb3.pdf +1 -0
- package/public/uploads/e4cce295-0d85-4525-a1b0-a61c45722e26.pdf +1 -0
- package/public/uploads/eb4df45e-563c-48b8-9c68-c18212312426.pdf +1 -0
- package/public/vercel.svg +1 -0
- package/public/widget/embed.js +249 -0
- package/public/window.svg +1 -0
- package/scripts/backup-db.sh +57 -0
- package/scripts/backup-db.ts +24 -0
- package/scripts/generate-openapi.ts +22 -0
- package/scripts/migration-helper.ts +39 -0
- package/scripts/pre-deploy.ts +75 -0
- package/scripts/restore-db.sh +60 -0
- package/scripts/rollback.ts +72 -0
- package/scripts/seed-tags.ts +48 -0
- package/tests/api/feedback-bulk.test.ts +47 -0
- package/tests/api/feedback-by-id.test.ts +67 -0
- package/tests/api/feedback-comments-route-import.test.ts +26 -0
- package/tests/api/feedback-create.test.ts +71 -0
- package/tests/api/feedback-delete.test.ts +160 -0
- package/tests/api/feedback-filter.test.ts +250 -0
- package/tests/api/feedback-list.test.ts +234 -0
- package/tests/api/feedback-route-assignee-condition.test.ts +32 -0
- package/tests/api/feedback-similar.test.ts +46 -0
- package/tests/api/feedback-sort.test.ts +261 -0
- package/tests/api/feedback-status-enum.test.ts +49 -0
- package/tests/api/feedback-status-filter.test.ts +117 -0
- package/tests/api/feedback-submit-on-behalf.test.ts +269 -0
- package/tests/api/feedback.test.ts +175 -0
- package/tests/api/identify-jwt.test.ts +25 -0
- package/tests/api/invitation-accept.test.ts +213 -0
- package/tests/api/organization-invitations.test.ts +186 -0
- package/tests/api/organization-members-list.test.ts +79 -0
- package/tests/api/organization-members.test.ts +340 -0
- package/tests/api/organizations.test.ts +149 -0
- package/tests/api/register.test.ts +112 -0
- package/tests/api/upload.test.ts +103 -0
- package/tests/api/vote.test.ts +82 -0
- package/tests/app/admin-feedback-detail-page.test.tsx +25 -0
- package/tests/app/admin-feedback-list-page.test.tsx +25 -0
- package/tests/app/admin-feedback-new-page.test.tsx +25 -0
- package/tests/app/health-route-helpers.test.ts +27 -0
- package/tests/app/login-page.test.ts +26 -0
- package/tests/app/portal-page.test.ts +29 -0
- package/tests/app/project-portal-overview.test.tsx +25 -0
- package/tests/app/widget-page-import.test.ts +25 -0
- package/tests/components/create-post-dialog-defaults.test.ts +43 -0
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +27 -0
- package/tests/components/feedback/embedded-feedback-form.test.tsx +96 -0
- package/tests/components/feedback/feedback-detail.test.tsx +25 -0
- package/tests/components/feedback/feedback-stats.test.tsx +49 -0
- package/tests/components/feedback-bulk-actions.test.tsx +39 -0
- package/tests/components/feedback-i18n-keys.test.ts +70 -0
- package/tests/components/feedback-list-controls-compile.test.ts +25 -0
- package/tests/components/feedback-list-controls.test.tsx +204 -0
- package/tests/components/feedback-list-item.test.tsx +67 -0
- package/tests/components/landing/hero.test.tsx +46 -0
- package/tests/components/layout/language-switcher.test.tsx +25 -0
- package/tests/components/layout/sidebar.test.tsx +157 -0
- package/tests/components/login-form.test.ts +25 -0
- package/tests/components/organization-form.test.ts +32 -0
- package/tests/components/organization-switcher.test.ts +25 -0
- package/tests/components/pagination.test.tsx +43 -0
- package/tests/components/portal-overview.test.tsx +25 -0
- package/tests/components/profile-form.test.tsx +139 -0
- package/tests/components/role-selector.test.ts +31 -0
- package/tests/components/status-chart.test.tsx +90 -0
- package/tests/e2e/auth.e2e.ts +323 -0
- package/tests/e2e/feedback-actions.e2e.ts +471 -0
- package/tests/e2e/feedback-attachment.e2e.ts +168 -0
- package/tests/e2e/feedback-customer.e2e.ts +226 -0
- package/tests/e2e/feedback-management.e2e.ts +565 -0
- package/tests/e2e/feedback-submit.e2e.ts +133 -0
- package/tests/e2e/feedback-view.e2e.ts +297 -0
- package/tests/e2e/fixtures/test-data.ts +235 -0
- package/tests/e2e/health-check.e2e.ts +230 -0
- package/tests/e2e/helpers/test-utils-helpers.test.ts +43 -0
- package/tests/e2e/helpers/test-utils.ts +298 -0
- package/tests/e2e/integration-placeholders.e2e.ts +199 -0
- package/tests/e2e/organization.e2e.ts +292 -0
- package/tests/e2e/permissions.e2e.ts +424 -0
- package/tests/e2e/project-widget.e2e.ts +63 -0
- package/tests/feedback/filters.test.ts +29 -0
- package/tests/hooks/use-permissions.test.ts +52 -0
- package/tests/lib/ai/classifier.test.ts +104 -0
- package/tests/lib/ai/duplicate-detector.test.ts +234 -0
- package/tests/lib/attachments-schema.test.ts +30 -0
- package/tests/lib/auth/session.test.ts +49 -0
- package/tests/lib/auth-client.test.ts +37 -0
- package/tests/lib/auth-config.test.ts +26 -0
- package/tests/lib/feedback-prefill.test.ts +52 -0
- package/tests/lib/feedback-processor.test.ts +41 -0
- package/tests/lib/feedback-schema.test.ts +33 -0
- package/tests/lib/file-validator.test.ts +48 -0
- package/tests/lib/get-feedback-by-id.test.ts +37 -0
- package/tests/lib/invitations.test.ts +35 -0
- package/tests/lib/login-schema.test.ts +36 -0
- package/tests/lib/org-context.test.ts +95 -0
- package/tests/lib/organization-access.test.ts +44 -0
- package/tests/lib/organization-member-role-schema.test.ts +41 -0
- package/tests/lib/permissions.test.ts +88 -0
- package/tests/lib/portal-analytics.test.ts +25 -0
- package/tests/lib/portal-contributors.test.ts +25 -0
- package/tests/lib/portal-copy.test.ts +27 -0
- package/tests/lib/portal-i18n.test.ts +30 -0
- package/tests/lib/portal-leaderboard-settings.test.ts +25 -0
- package/tests/lib/portal-modules.test.ts +25 -0
- package/tests/lib/portal-seo.test.ts +25 -0
- package/tests/lib/portal-sharing.test.ts +25 -0
- package/tests/lib/portal-sorting.test.ts +25 -0
- package/tests/lib/portal-theme.test.ts +25 -0
- package/tests/lib/rate-limit.test.ts +142 -0
- package/tests/lib/resolve-locale.test.ts +34 -0
- package/tests/lib/services/backup.test.ts +145 -0
- package/tests/lib/user-organizations.test.ts +42 -0
- package/tests/lib/user-role-schema.test.ts +33 -0
- package/tests/lib/user-schema.test.ts +25 -0
- package/tests/setup.ts +74 -0
- package/tsconfig.json +34 -0
- 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
|
+

|
|
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
|
+
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
14
|
+
[](https://github.com/nexttylabs/echo/stargazers)
|
|
15
|
+
[](https://github.com/nexttylabs/echo/network)
|
|
16
|
+
[](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
|
+
}
|