@nexttylabs/echo 0.4.0 → 0.6.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/CHANGELOG.md +27 -0
- package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +12 -6
- package/app/(dashboard)/admin/feedback/new/page.tsx +19 -17
- package/app/(dashboard)/admin/layout.tsx +16 -6
- package/app/(dashboard)/layout.tsx +4 -2
- package/app/(dashboard)/settings/api-keys/page.tsx +13 -3
- package/app/(dashboard)/settings/layout.tsx +25 -2
- package/app/(dashboard)/settings/organization/page.tsx +8 -9
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/app/api/feedback/[id]/comments/[commentId]/route.ts +13 -4
- package/app/api/feedback/[id]/reclassify/route.ts +4 -4
- package/app/api/organizations/handler.ts +2 -4
- package/components/settings/settings-sidebar.tsx +4 -4
- package/hooks/use-organization.tsx +116 -0
- package/hooks/use-permissions.ts +24 -11
- package/lib/auth/config.ts +0 -7
- package/lib/auth/organization.ts +20 -0
- package/lib/auth/permissions.ts +10 -0
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
# Portal Experience Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Deliver the portal experience (IA shell + submission prefill + inline duplicate suggestions) as a cohesive, test-driven rollout.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Add a public `app/portal` route with lightweight presentational components. Enhance the embedded feedback form with URL prefill parsing and inline duplicate suggestions backed by a similarity API route that reuses existing duplicate detection logic.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Next.js App Router, React 19, TypeScript, Tailwind v4, shadcn/ui, Drizzle, bun:test
|
|
10
|
-
|
|
11
|
-
**Featurebase-aligned portal requirements (for scope decisions):**
|
|
12
|
-
- 反馈板块(Boards)与 AI 自动分配
|
|
13
|
-
- 排序与筛选:Top/Recent/Trending;按状态/板块/标签/ETA/创建日期过滤
|
|
14
|
-
- 默认排序可配置
|
|
15
|
-
- 贡献者榜单:按投票/评论/被采纳计分;可隐藏/匿名化
|
|
16
|
-
- 门户分享方式:链接 / Widget(含截图工具)/ 嵌入式 Portal
|
|
17
|
-
- 门户文案与 CTA 可配置(含板块级覆盖)
|
|
18
|
-
- 门户多语言(自动检测 + 手动切换)与可选自动翻译
|
|
19
|
-
- 反馈分析:按日/周/月统计;Top viewed/voted/commented;Widget vs Portal 来源
|
|
20
|
-
- 门户模块开关(禁用反馈视图)
|
|
21
|
-
|
|
22
|
-
### Task 1: Add portal route + shell component
|
|
23
|
-
|
|
24
|
-
**Files:**
|
|
25
|
-
- Create: `app/portal/page.tsx`
|
|
26
|
-
- Create: `components/portal/portal-shell.tsx`
|
|
27
|
-
- Create: `components/portal/portal-nav.tsx`
|
|
28
|
-
- Test: `tests/app/portal-page.test.ts`
|
|
29
|
-
|
|
30
|
-
**Step 1: Write the failing test**
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
import { describe, expect, it } from "bun:test";
|
|
34
|
-
import PortalPage, {
|
|
35
|
-
PORTAL_TITLE,
|
|
36
|
-
PORTAL_SECTIONS,
|
|
37
|
-
} from "@/app/portal/page";
|
|
38
|
-
|
|
39
|
-
describe("portal page", () => {
|
|
40
|
-
it("exports a function", () => {
|
|
41
|
-
expect(typeof PortalPage).toBe("function");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("exposes portal labels", () => {
|
|
45
|
-
expect(PORTAL_TITLE).toBe("反馈中心");
|
|
46
|
-
expect(PORTAL_SECTIONS).toEqual([
|
|
47
|
-
{ label: "反馈", href: "/portal" },
|
|
48
|
-
{ label: "路线图", href: "/portal/roadmap" },
|
|
49
|
-
{ label: "变更日志", href: "/portal/changelog" },
|
|
50
|
-
{ label: "帮助中心", href: "/portal/help" },
|
|
51
|
-
]);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Step 2: Run test to verify it fails**
|
|
57
|
-
|
|
58
|
-
Run: `bun test tests/app/portal-page.test.ts`
|
|
59
|
-
Expected: FAIL with "Cannot find module '@/app/portal/page'"
|
|
60
|
-
|
|
61
|
-
**Step 3: Write minimal implementation**
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
export const PORTAL_TITLE = "反馈中心";
|
|
65
|
-
export const PORTAL_SECTIONS = [
|
|
66
|
-
{ label: "反馈", href: "/portal" },
|
|
67
|
-
{ label: "路线图", href: "/portal/roadmap" },
|
|
68
|
-
{ label: "变更日志", href: "/portal/changelog" },
|
|
69
|
-
{ label: "帮助中心", href: "/portal/help" },
|
|
70
|
-
] as const;
|
|
71
|
-
|
|
72
|
-
export default function PortalPage() {
|
|
73
|
-
return (
|
|
74
|
-
<PortalShell title={PORTAL_TITLE} sections={PORTAL_SECTIONS} />
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Step 4: Run test to verify it passes**
|
|
80
|
-
|
|
81
|
-
Run: `bun test tests/app/portal-page.test.ts`
|
|
82
|
-
Expected: PASS
|
|
83
|
-
|
|
84
|
-
**Step 5: Commit**
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
git add app/portal/page.tsx components/portal/portal-shell.tsx components/portal/portal-nav.tsx tests/app/portal-page.test.ts
|
|
88
|
-
|
|
89
|
-
git commit -m "feat: add portal shell route"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Task 2: Link public feedback detail back to portal
|
|
93
|
-
|
|
94
|
-
**Files:**
|
|
95
|
-
- Modify: `app/feedback/[id]/page.tsx`
|
|
96
|
-
- Test: `tests/app/feedback-detail-page.test.ts`
|
|
97
|
-
|
|
98
|
-
**Step 1: Write the failing test**
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
import { describe, expect, it } from "bun:test";
|
|
102
|
-
import { FEEDBACK_BACK_HOME_LABEL } from "@/app/feedback/[id]/page";
|
|
103
|
-
|
|
104
|
-
describe("feedback detail page", () => {
|
|
105
|
-
it("keeps back label unchanged", () => {
|
|
106
|
-
expect(FEEDBACK_BACK_HOME_LABEL).toBe("返回首页");
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Step 2: Run test to verify it fails**
|
|
112
|
-
|
|
113
|
-
Run: `bun test tests/app/feedback-detail-page.test.ts`
|
|
114
|
-
Expected: PASS (baseline coverage) — then update test expectations if label changes.
|
|
115
|
-
|
|
116
|
-
**Step 3: Write minimal implementation**
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
<Button asChild variant="outline" size="sm">
|
|
120
|
-
<Link href="/portal">{FEEDBACK_BACK_HOME_LABEL}</Link>
|
|
121
|
-
</Button>
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Step 4: Run test to verify it passes**
|
|
125
|
-
|
|
126
|
-
Run: `bun test tests/app/feedback-detail-page.test.ts`
|
|
127
|
-
Expected: PASS
|
|
128
|
-
|
|
129
|
-
**Step 5: Commit**
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
git add app/feedback/[id]/page.tsx tests/app/feedback-detail-page.test.ts
|
|
133
|
-
|
|
134
|
-
git commit -m "feat: link feedback detail back to portal"
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Task 3: Add prefill parser utility
|
|
138
|
-
|
|
139
|
-
**Files:**
|
|
140
|
-
- Create: `lib/feedback/prefill.ts`
|
|
141
|
-
- Test: `tests/lib/feedback-prefill.test.ts`
|
|
142
|
-
|
|
143
|
-
**Step 1: Write the failing test**
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
import { describe, expect, it } from "bun:test";
|
|
147
|
-
import { parseFeedbackPrefill } from "@/lib/feedback/prefill";
|
|
148
|
-
|
|
149
|
-
const params = new URLSearchParams({
|
|
150
|
-
title: "导出报告错位",
|
|
151
|
-
description: "PDF 排版错位",
|
|
152
|
-
type: "bug",
|
|
153
|
-
priority: "high",
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe("parseFeedbackPrefill", () => {
|
|
157
|
-
it("parses known fields", () => {
|
|
158
|
-
expect(parseFeedbackPrefill(params)).toEqual({
|
|
159
|
-
title: "导出报告错位",
|
|
160
|
-
description: "PDF 排版错位",
|
|
161
|
-
type: "bug",
|
|
162
|
-
priority: "high",
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("ignores unknown fields", () => {
|
|
167
|
-
const extra = new URLSearchParams({ title: "x", source: "email" });
|
|
168
|
-
expect(parseFeedbackPrefill(extra)).toEqual({ title: "x" });
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**Step 2: Run test to verify it fails**
|
|
174
|
-
|
|
175
|
-
Run: `bun test tests/lib/feedback-prefill.test.ts`
|
|
176
|
-
Expected: FAIL with "Cannot find module '@/lib/feedback/prefill'"
|
|
177
|
-
|
|
178
|
-
**Step 3: Write minimal implementation**
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
import { feedbackTypeEnum, priorityEnum } from "@/lib/validators/feedback";
|
|
182
|
-
|
|
183
|
-
type Prefill = Partial<{
|
|
184
|
-
title: string;
|
|
185
|
-
description: string;
|
|
186
|
-
type: "bug" | "feature" | "issue" | "other";
|
|
187
|
-
priority: "low" | "medium" | "high";
|
|
188
|
-
}>;
|
|
189
|
-
|
|
190
|
-
export function parseFeedbackPrefill(params: URLSearchParams): Prefill {
|
|
191
|
-
const prefill: Prefill = {};
|
|
192
|
-
const title = params.get("title");
|
|
193
|
-
const description = params.get("description");
|
|
194
|
-
const type = params.get("type");
|
|
195
|
-
const priority = params.get("priority");
|
|
196
|
-
|
|
197
|
-
if (title) prefill.title = title;
|
|
198
|
-
if (description) prefill.description = description;
|
|
199
|
-
if (type && feedbackTypeEnum.safeParse(type).success) {
|
|
200
|
-
prefill.type = type as Prefill["type"];
|
|
201
|
-
}
|
|
202
|
-
if (priority && priorityEnum.safeParse(priority).success) {
|
|
203
|
-
prefill.priority = priority as Prefill["priority"];
|
|
204
|
-
}
|
|
205
|
-
return prefill;
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Step 4: Run test to verify it passes**
|
|
210
|
-
|
|
211
|
-
Run: `bun test tests/lib/feedback-prefill.test.ts`
|
|
212
|
-
Expected: PASS
|
|
213
|
-
|
|
214
|
-
**Step 5: Commit**
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
git add lib/feedback/prefill.ts tests/lib/feedback-prefill.test.ts
|
|
218
|
-
|
|
219
|
-
git commit -m "feat: add feedback prefill parser"
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Task 4: Wire prefill into embedded feedback form
|
|
223
|
-
|
|
224
|
-
**Files:**
|
|
225
|
-
- Modify: `components/feedback/embedded-feedback-form.tsx`
|
|
226
|
-
- Modify: `tests/components/feedback/embedded-feedback-form.test.tsx`
|
|
227
|
-
|
|
228
|
-
**Step 1: Write the failing test**
|
|
229
|
-
|
|
230
|
-
```ts
|
|
231
|
-
import { describe, expect, it } from "bun:test";
|
|
232
|
-
import { FEEDBACK_PREFILL_KEYS } from "@/components/feedback/embedded-feedback-form";
|
|
233
|
-
|
|
234
|
-
describe("EmbeddedFeedbackForm prefill", () => {
|
|
235
|
-
it("exposes prefill keys", () => {
|
|
236
|
-
expect(FEEDBACK_PREFILL_KEYS).toEqual([
|
|
237
|
-
"title",
|
|
238
|
-
"description",
|
|
239
|
-
"type",
|
|
240
|
-
"priority",
|
|
241
|
-
]);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
**Step 2: Run test to verify it fails**
|
|
247
|
-
|
|
248
|
-
Run: `bun test tests/components/feedback/embedded-feedback-form.test.tsx`
|
|
249
|
-
Expected: FAIL with "FEEDBACK_PREFILL_KEYS is not defined"
|
|
250
|
-
|
|
251
|
-
**Step 3: Write minimal implementation**
|
|
252
|
-
|
|
253
|
-
```tsx
|
|
254
|
-
export const FEEDBACK_PREFILL_KEYS = [
|
|
255
|
-
"title",
|
|
256
|
-
"description",
|
|
257
|
-
"type",
|
|
258
|
-
"priority",
|
|
259
|
-
] as const;
|
|
260
|
-
|
|
261
|
-
const searchParams = useSearchParams();
|
|
262
|
-
const prefill = useMemo(
|
|
263
|
-
() => (searchParams ? parseFeedbackPrefill(searchParams) : {}),
|
|
264
|
-
[searchParams]
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const form = useForm<BaseFeedbackInput>({
|
|
268
|
-
resolver: zodResolver(baseFeedbackSchema),
|
|
269
|
-
defaultValues: {
|
|
270
|
-
title: prefill.title ?? "",
|
|
271
|
-
description: prefill.description ?? "",
|
|
272
|
-
type: prefill.type ?? "issue",
|
|
273
|
-
priority: prefill.priority ?? "medium",
|
|
274
|
-
},
|
|
275
|
-
mode: "onBlur",
|
|
276
|
-
});
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Step 4: Run test to verify it passes**
|
|
280
|
-
|
|
281
|
-
Run: `bun test tests/components/feedback/embedded-feedback-form.test.tsx`
|
|
282
|
-
Expected: PASS
|
|
283
|
-
|
|
284
|
-
**Step 5: Commit**
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
git add components/feedback/embedded-feedback-form.tsx tests/components/feedback/embedded-feedback-form.test.tsx
|
|
288
|
-
|
|
289
|
-
git commit -m "feat: support feedback form prefill"
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Task 5: Add similarity API route + helper
|
|
293
|
-
|
|
294
|
-
**Files:**
|
|
295
|
-
- Create: `app/api/feedback/similar/route.ts`
|
|
296
|
-
- Create: `lib/feedback/find-similar.ts`
|
|
297
|
-
- Test: `tests/api/feedback-similar.test.ts`
|
|
298
|
-
|
|
299
|
-
**Step 1: Write the failing test**
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
import { describe, expect, it } from "bun:test";
|
|
303
|
-
import { buildSimilarResponse } from "@/lib/feedback/find-similar";
|
|
304
|
-
|
|
305
|
-
const sample = [{
|
|
306
|
-
feedbackId: 1,
|
|
307
|
-
title: "批量导入联系人",
|
|
308
|
-
description: "CSV 导入",
|
|
309
|
-
}];
|
|
310
|
-
|
|
311
|
-
describe("buildSimilarResponse", () => {
|
|
312
|
-
it("returns scored suggestions", () => {
|
|
313
|
-
const result = buildSimilarResponse("导入联系人", "", sample);
|
|
314
|
-
expect(result.length).toBeGreaterThan(0);
|
|
315
|
-
expect(result[0]).toHaveProperty("feedbackId", 1);
|
|
316
|
-
expect(result[0]).toHaveProperty("similarity");
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
**Step 2: Run test to verify it fails**
|
|
322
|
-
|
|
323
|
-
Run: `bun test tests/api/feedback-similar.test.ts`
|
|
324
|
-
Expected: FAIL with "Cannot find module '@/lib/feedback/find-similar'"
|
|
325
|
-
|
|
326
|
-
**Step 3: Write minimal implementation**
|
|
327
|
-
|
|
328
|
-
```ts
|
|
329
|
-
import { findDuplicates } from "@/lib/services/ai/duplicate-detector";
|
|
330
|
-
|
|
331
|
-
export function buildSimilarResponse(
|
|
332
|
-
title: string,
|
|
333
|
-
description: string,
|
|
334
|
-
existing: Array<{ feedbackId: number; title: string; description?: string | null }>
|
|
335
|
-
) {
|
|
336
|
-
return findDuplicates(
|
|
337
|
-
{ title, description },
|
|
338
|
-
existing.map((item) => ({
|
|
339
|
-
feedbackId: item.feedbackId,
|
|
340
|
-
title: item.title,
|
|
341
|
-
description: item.description ?? "",
|
|
342
|
-
}))
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
**Step 4: Run test to verify it passes**
|
|
348
|
-
|
|
349
|
-
Run: `bun test tests/api/feedback-similar.test.ts`
|
|
350
|
-
Expected: PASS
|
|
351
|
-
|
|
352
|
-
**Step 5: Commit**
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
git add app/api/feedback/similar/route.ts lib/feedback/find-similar.ts tests/api/feedback-similar.test.ts
|
|
356
|
-
|
|
357
|
-
git commit -m "feat: add feedback similarity endpoint"
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### Task 6: Render inline duplicate suggestions in the form
|
|
361
|
-
|
|
362
|
-
**Files:**
|
|
363
|
-
- Create: `components/feedback/duplicate-suggestions-inline.tsx`
|
|
364
|
-
- Modify: `components/feedback/embedded-feedback-form.tsx`
|
|
365
|
-
- Test: `tests/components/feedback/duplicate-suggestions-inline.test.tsx`
|
|
366
|
-
|
|
367
|
-
**Step 1: Write the failing test**
|
|
368
|
-
|
|
369
|
-
```ts
|
|
370
|
-
import { describe, expect, it } from "bun:test";
|
|
371
|
-
import { DuplicateSuggestionsInline } from "@/components/feedback/duplicate-suggestions-inline";
|
|
372
|
-
|
|
373
|
-
const isFn = (value: unknown) => typeof value === "function";
|
|
374
|
-
|
|
375
|
-
describe("DuplicateSuggestionsInline", () => {
|
|
376
|
-
it("exports a component", () => {
|
|
377
|
-
expect(isFn(DuplicateSuggestionsInline)).toBe(true);
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**Step 2: Run test to verify it fails**
|
|
383
|
-
|
|
384
|
-
Run: `bun test tests/components/feedback/duplicate-suggestions-inline.test.tsx`
|
|
385
|
-
Expected: FAIL with "Cannot find module '@/components/feedback/duplicate-suggestions-inline'"
|
|
386
|
-
|
|
387
|
-
**Step 3: Write minimal implementation**
|
|
388
|
-
|
|
389
|
-
```tsx
|
|
390
|
-
export function DuplicateSuggestionsInline({ query }: { query: string }) {
|
|
391
|
-
// fetch `/api/feedback/similar?title=${encodeURIComponent(query)}`
|
|
392
|
-
// render a compact list linking to `/feedback/:id`
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
**Step 4: Run test to verify it passes**
|
|
398
|
-
|
|
399
|
-
Run: `bun test tests/components/feedback/duplicate-suggestions-inline.test.tsx`
|
|
400
|
-
Expected: PASS
|
|
401
|
-
|
|
402
|
-
**Step 5: Commit**
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
git add components/feedback/duplicate-suggestions-inline.tsx components/feedback/embedded-feedback-form.tsx tests/components/feedback/duplicate-suggestions-inline.test.tsx
|
|
406
|
-
|
|
407
|
-
git commit -m "feat: show inline duplicate suggestions"
|
|
408
|
-
```
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
# 项目删除功能设计
|
|
2
|
-
|
|
3
|
-
**日期**: 2026-01-09
|
|
4
|
-
**状态**: 已批准
|
|
5
|
-
|
|
6
|
-
## 概述
|
|
7
|
-
|
|
8
|
-
为项目管理功能添加删除能力,允许管理员安全地删除项目。使用输入项目名称确认的方式防止误删除。
|
|
9
|
-
|
|
10
|
-
## 需求
|
|
11
|
-
|
|
12
|
-
- 只有管理员(admin)可以删除项目
|
|
13
|
-
- 删除前必须输入完整项目名称进行确认
|
|
14
|
-
- 删除成功后跳转到项目列表页面
|
|
15
|
-
- 删除是永久性的,会级联删除所有相关数据(通过数据库外键 onDelete: cascade)
|
|
16
|
-
- 显示警告信息,告知用户删除的后果
|
|
17
|
-
|
|
18
|
-
## 技术实现
|
|
19
|
-
|
|
20
|
-
### 1. 后端 API
|
|
21
|
-
|
|
22
|
-
**已存在**: `/api/projects/[projectId]` DELETE 端点
|
|
23
|
-
- 权限检查:仅限 admin 角色
|
|
24
|
-
- 数据库删除:使用 Drizzle ORM 删除项目记录
|
|
25
|
-
- 级联删除:通过数据库外键自动处理(projects.organizationId 引用 organizations.id,设置了 onDelete: cascade)
|
|
26
|
-
|
|
27
|
-
### 2. 前端组件
|
|
28
|
-
|
|
29
|
-
#### 2.1 删除按钮区域(ProjectSettings 组件)
|
|
30
|
-
|
|
31
|
-
**位置**: `components/project/project-settings.tsx`
|
|
32
|
-
|
|
33
|
-
在现有的 Widget 配置卡片下方添加新的"危险区域"卡片:
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
<Card className="border-destructive/50">
|
|
37
|
-
<CardHeader>
|
|
38
|
-
<CardTitle className="text-destructive">危险区域</CardTitle>
|
|
39
|
-
<CardDescription>
|
|
40
|
-
这些操作不可逆,请谨慎操作
|
|
41
|
-
</CardDescription>
|
|
42
|
-
</CardHeader>
|
|
43
|
-
<CardContent>
|
|
44
|
-
<div className="flex items-start justify-between">
|
|
45
|
-
<div>
|
|
46
|
-
<h4 className="font-medium">删除项目</h4>
|
|
47
|
-
<p className="text-sm text-muted-foreground">
|
|
48
|
-
永久删除此项目及其所有相关数据
|
|
49
|
-
</p>
|
|
50
|
-
</div>
|
|
51
|
-
<Button variant="destructive" onClick={openDeleteDialog}>
|
|
52
|
-
删除项目
|
|
53
|
-
</Button>
|
|
54
|
-
</div>
|
|
55
|
-
</CardContent>
|
|
56
|
-
</Card>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
#### 2.2 删除确认对话框组件
|
|
60
|
-
|
|
61
|
-
**新建组件**: `components/project/delete-project-dialog.tsx`
|
|
62
|
-
|
|
63
|
-
**功能要求**:
|
|
64
|
-
- 使用 shadcn Dialog 组件
|
|
65
|
-
- 显示警告图标和警告文本
|
|
66
|
-
- 包含输入框,要求用户输入完整项目名称
|
|
67
|
-
- 实时验证输入内容
|
|
68
|
-
- 只有输入完全匹配时才启用删除按钮
|
|
69
|
-
- 显示加载状态(删除进行中)
|
|
70
|
-
- 删除成功后跳转到 `/settings/projects`
|
|
71
|
-
|
|
72
|
-
**对话框内容**:
|
|
73
|
-
```
|
|
74
|
-
标题: 删除项目
|
|
75
|
-
图标: AlertTriangle(红色)
|
|
76
|
-
|
|
77
|
-
警告文本:
|
|
78
|
-
此操作不可撤销。删除项目将会:
|
|
79
|
-
• 永久删除项目的所有配置
|
|
80
|
-
• 删除所有相关的反馈数据
|
|
81
|
-
• 删除所有相关的评论和附件
|
|
82
|
-
|
|
83
|
-
输入框:
|
|
84
|
-
请输入项目名称 "{projectName}" 以确认删除
|
|
85
|
-
|
|
86
|
-
按钮:
|
|
87
|
-
- 取消(outline variant)
|
|
88
|
-
- 删除项目(destructive variant,仅在输入正确时可用)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### 3. 状态管理和错误处理
|
|
92
|
-
|
|
93
|
-
**状态**:
|
|
94
|
-
- `isOpen`: 对话框是否打开
|
|
95
|
-
- `isDeleting`: 是否正在删除
|
|
96
|
-
- `confirmText`: 用户输入的确认文本
|
|
97
|
-
- `error`: 删除错误信息
|
|
98
|
-
|
|
99
|
-
**错误处理**:
|
|
100
|
-
- 网络错误:显示错误提示,不关闭对话框
|
|
101
|
-
- 权限错误:显示"权限不足"错误
|
|
102
|
-
- 项目不存在:显示"项目未找到"错误
|
|
103
|
-
|
|
104
|
-
### 4. 用户流程
|
|
105
|
-
|
|
106
|
-
1. 用户在项目设置页面向下滚动到"危险区域"
|
|
107
|
-
2. 点击"删除项目"按钮
|
|
108
|
-
3. 弹出确认对话框
|
|
109
|
-
4. 查看警告信息
|
|
110
|
-
5. 输入完整项目名称
|
|
111
|
-
6. 点击"删除项目"按钮(此时已启用)
|
|
112
|
-
7. 显示加载状态
|
|
113
|
-
8. 删除成功后自动跳转到 `/settings/projects`
|
|
114
|
-
9. 显示成功提示(toast)
|
|
115
|
-
|
|
116
|
-
## UI/UX 设计
|
|
117
|
-
|
|
118
|
-
### 视觉层次
|
|
119
|
-
- 危险区域使用浅红色边框突出显示
|
|
120
|
-
- 删除按钮使用 destructive variant(红色背景)
|
|
121
|
-
- 对话框中的警告图标使用红色
|
|
122
|
-
- 确认输入框下方显示灰色提示文本
|
|
123
|
-
|
|
124
|
-
### 可访问性
|
|
125
|
-
- 对话框支持键盘导航(ESC 关闭,Tab 切换)
|
|
126
|
-
- 表单提交使用 Enter 键(输入正确时)
|
|
127
|
-
- 使用 ARIA 标签标记警告区域
|
|
128
|
-
- 输入框有明确的 label 和 placeholder
|
|
129
|
-
|
|
130
|
-
## 文件清单
|
|
131
|
-
|
|
132
|
-
### 新建文件
|
|
133
|
-
- `components/project/delete-project-dialog.tsx` - 删除确认对话框组件
|
|
134
|
-
|
|
135
|
-
### 修改文件
|
|
136
|
-
- `components/project/project-settings.tsx` - 添加危险区域和删除按钮
|
|
137
|
-
|
|
138
|
-
### 依赖组件(已存在)
|
|
139
|
-
- `components/ui/dialog.tsx` - shadcn Dialog 组件
|
|
140
|
-
- `components/ui/button.tsx` - shadcn Button 组件
|
|
141
|
-
- `components/ui/card.tsx` - shadcn Card 组件
|
|
142
|
-
- `components/ui/input.tsx` - shadcn Input 组件
|
|
143
|
-
|
|
144
|
-
## 安全考虑
|
|
145
|
-
|
|
146
|
-
1. **权限控制**: 后端 API 已实现,仅 admin 可删除
|
|
147
|
-
2. **确认机制**: 必须输入完整项目名称,防止误操作
|
|
148
|
-
3. **不可逆警告**: 明确告知用户删除后果
|
|
149
|
-
4. **级联删除**: 数据库层面自动处理,避免孤立数据
|
|
150
|
-
|
|
151
|
-
## 测试要点
|
|
152
|
-
|
|
153
|
-
1. **权限测试**: 非 admin 用户不应看到删除按钮
|
|
154
|
-
2. **输入验证**: 只有完全匹配才能启用删除按钮
|
|
155
|
-
3. **删除成功**: 项目被删除并跳转到列表页
|
|
156
|
-
4. **错误处理**: 网络错误、权限错误等正确显示
|
|
157
|
-
5. **级联删除**: 相关数据正确删除(需数据库层面验证)
|
|
158
|
-
|
|
159
|
-
## 未来优化
|
|
160
|
-
|
|
161
|
-
1. 软删除:考虑添加 deletedAt 字段实现软删除
|
|
162
|
-
2. 删除前统计:显示将被删除的反馈数量
|
|
163
|
-
3. 导出备份:删除前允许导出项目数据
|