@nexttylabs/echo 0.4.0 → 0.5.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 (247) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
  3. package/app/api/admin/backup/route.ts +22 -4
  4. package/app/api/auth/register/handler.ts +1 -2
  5. package/lib/auth/config.ts +0 -7
  6. package/lib/db/migrations/0000_needy_leech.sql +335 -0
  7. package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
  8. package/lib/db/migrations/meta/_journal.json +2 -135
  9. package/lib/db/schema/auth.ts +0 -1
  10. package/lib/db/schema/index.ts +0 -1
  11. package/lib/portal/public-context.tsx +5 -0
  12. package/package.json +20 -1
  13. package/.changeset/README.md +0 -21
  14. package/.changeset/config.json +0 -11
  15. package/.changeset/cozy-ghosts-care.md +0 -5
  16. package/.changeset/sharp-lines-stand.md +0 -5
  17. package/.changeset/sour-doodles-eat.md +0 -5
  18. package/.changeset/tender-moose-shop.md +0 -5
  19. package/.github/pull_request_template.md +0 -13
  20. package/.github/workflows/ci.yml +0 -41
  21. package/.github/workflows/publish.yml +0 -44
  22. package/.github/workflows/release.yml +0 -73
  23. package/AGENTS.md +0 -92
  24. package/Dockerfile +0 -57
  25. package/Makefile +0 -77
  26. package/bun.lock +0 -2503
  27. package/components/portal/project-switcher.tsx +0 -20
  28. package/docker-compose.dev.yml +0 -26
  29. package/docker-compose.yml +0 -98
  30. package/docs/architecture.md +0 -259
  31. package/docs/component-inventory.md +0 -261
  32. package/docs/database-migrations.md +0 -76
  33. package/docs/development-guide.md +0 -209
  34. package/docs/e2e-user-flows.csv +0 -31
  35. package/docs/er-diagram-feedback.mmd +0 -138
  36. package/docs/er-diagram.mmd +0 -281
  37. package/docs/i18n-check-report.md +0 -296
  38. package/docs/index.md +0 -214
  39. package/docs/logic-chain.md +0 -94
  40. package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
  41. package/docs/plans/2026-01-02-user-login-design.md +0 -37
  42. package/docs/plans/2026-01-02-user-login.md +0 -437
  43. package/docs/plans/2026-01-02-user-registration-design.md +0 -47
  44. package/docs/plans/2026-01-02-user-registration.md +0 -628
  45. package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
  46. package/docs/plans/2026-01-03-roles-permissions.md +0 -266
  47. package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
  48. package/docs/plans/2026-01-05-member-removal.md +0 -186
  49. package/docs/plans/2026-01-05-organization-creation.md +0 -374
  50. package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
  51. package/docs/plans/2026-01-05-role-configuration.md +0 -441
  52. package/docs/plans/2026-01-06-file-upload-support.md +0 -804
  53. package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
  54. package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
  55. package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
  56. package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
  57. package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
  58. package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
  59. package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
  60. package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
  61. package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
  62. package/docs/plans/2026-01-09-settings-center-design.md +0 -114
  63. package/docs/plans/2026-01-09-settings-center.md +0 -948
  64. package/docs/plans/2026-01-10-organization-only-design.md +0 -66
  65. package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
  66. package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
  67. package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
  68. package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
  69. package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
  70. package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
  71. package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
  72. package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
  73. package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
  74. package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
  75. package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
  76. package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
  77. package/docs/plans/2026-01-18-changesets-design.md +0 -40
  78. package/docs/product_changes.md +0 -37
  79. package/docs/project-overview.md +0 -159
  80. package/docs/project-scan-report.json +0 -104
  81. package/docs/route-role-visibility.md +0 -51
  82. package/docs/source-tree-analysis.md +0 -150
  83. package/docs/testing/delete-project-manual-tests.md +0 -18
  84. package/docs/user-story-tracking.md +0 -191
  85. package/eslint.config.mjs +0 -19
  86. package/lib/db/migrations/.gitkeep +0 -0
  87. package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
  88. package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
  89. package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
  90. package/lib/db/migrations/0003_add_org_description.sql +0 -1
  91. package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
  92. package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
  93. package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
  94. package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
  95. package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
  96. package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
  97. package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
  98. package/lib/db/migrations/0010_kind_junta.sql +0 -8
  99. package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
  100. package/lib/db/migrations/0012_giant_power_man.sql +0 -24
  101. package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
  102. package/lib/db/migrations/0014_blue_alice.sql +0 -18
  103. package/lib/db/migrations/0015_webhook_tables.sql +0 -41
  104. package/lib/db/migrations/0016_github_integration.sql +0 -30
  105. package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
  106. package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
  107. package/lib/db/migrations/0018_same_spitfire.sql +0 -1
  108. package/lib/db/migrations/0019_jittery_loners.sql +0 -16
  109. package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
  110. package/lib/db/migrations/meta/0001_snapshot.json +0 -553
  111. package/lib/db/migrations/meta/0002_snapshot.json +0 -560
  112. package/lib/db/migrations/meta/0003_snapshot.json +0 -650
  113. package/lib/db/migrations/meta/0004_snapshot.json +0 -852
  114. package/lib/db/migrations/meta/0005_snapshot.json +0 -900
  115. package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
  116. package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
  117. package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
  118. package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
  119. package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
  120. package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
  121. package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
  122. package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
  123. package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
  124. package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
  125. package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
  126. package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
  127. package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
  128. package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
  129. package/lib/db/schema/projects.ts +0 -145
  130. package/lib/db/schema/user-profiles.ts +0 -31
  131. package/lib/validations/projects.ts +0 -49
  132. package/next-env.d.ts +0 -6
  133. package/playwright.config.ts +0 -44
  134. package/proxy.test.ts +0 -131
  135. package/proxy.ts +0 -116
  136. package/scripts/backup-db.sh +0 -57
  137. package/scripts/backup-db.ts +0 -24
  138. package/scripts/generate-openapi.ts +0 -22
  139. package/scripts/migration-helper.ts +0 -39
  140. package/scripts/pre-deploy.ts +0 -75
  141. package/scripts/restore-db.sh +0 -60
  142. package/scripts/rollback.ts +0 -72
  143. package/scripts/seed-tags.ts +0 -48
  144. package/tests/api/feedback-bulk.test.ts +0 -47
  145. package/tests/api/feedback-by-id.test.ts +0 -67
  146. package/tests/api/feedback-comments-route-import.test.ts +0 -26
  147. package/tests/api/feedback-create.test.ts +0 -71
  148. package/tests/api/feedback-delete.test.ts +0 -160
  149. package/tests/api/feedback-filter.test.ts +0 -250
  150. package/tests/api/feedback-list.test.ts +0 -234
  151. package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
  152. package/tests/api/feedback-similar.test.ts +0 -46
  153. package/tests/api/feedback-sort.test.ts +0 -261
  154. package/tests/api/feedback-status-enum.test.ts +0 -49
  155. package/tests/api/feedback-status-filter.test.ts +0 -117
  156. package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
  157. package/tests/api/feedback.test.ts +0 -175
  158. package/tests/api/identify-jwt.test.ts +0 -25
  159. package/tests/api/invitation-accept.test.ts +0 -213
  160. package/tests/api/organization-invitations.test.ts +0 -186
  161. package/tests/api/organization-members-list.test.ts +0 -79
  162. package/tests/api/organization-members.test.ts +0 -340
  163. package/tests/api/organizations.test.ts +0 -149
  164. package/tests/api/register.test.ts +0 -112
  165. package/tests/api/upload.test.ts +0 -103
  166. package/tests/api/vote.test.ts +0 -82
  167. package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
  168. package/tests/app/admin-feedback-list-page.test.tsx +0 -25
  169. package/tests/app/admin-feedback-new-page.test.tsx +0 -25
  170. package/tests/app/health-route-helpers.test.ts +0 -27
  171. package/tests/app/login-page.test.ts +0 -26
  172. package/tests/app/portal-page.test.ts +0 -29
  173. package/tests/app/project-portal-overview.test.tsx +0 -25
  174. package/tests/app/widget-page-import.test.ts +0 -25
  175. package/tests/components/create-post-dialog-defaults.test.ts +0 -43
  176. package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
  177. package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
  178. package/tests/components/feedback/feedback-detail.test.tsx +0 -25
  179. package/tests/components/feedback/feedback-stats.test.tsx +0 -49
  180. package/tests/components/feedback-bulk-actions.test.tsx +0 -39
  181. package/tests/components/feedback-i18n-keys.test.ts +0 -70
  182. package/tests/components/feedback-list-controls-compile.test.ts +0 -25
  183. package/tests/components/feedback-list-controls.test.tsx +0 -204
  184. package/tests/components/feedback-list-item.test.tsx +0 -67
  185. package/tests/components/landing/hero.test.tsx +0 -46
  186. package/tests/components/layout/language-switcher.test.tsx +0 -25
  187. package/tests/components/layout/sidebar.test.tsx +0 -157
  188. package/tests/components/login-form.test.ts +0 -25
  189. package/tests/components/organization-form.test.ts +0 -32
  190. package/tests/components/organization-switcher.test.ts +0 -25
  191. package/tests/components/pagination.test.tsx +0 -43
  192. package/tests/components/portal-overview.test.tsx +0 -25
  193. package/tests/components/profile-form.test.tsx +0 -139
  194. package/tests/components/role-selector.test.ts +0 -31
  195. package/tests/components/status-chart.test.tsx +0 -90
  196. package/tests/e2e/auth.e2e.ts +0 -323
  197. package/tests/e2e/feedback-actions.e2e.ts +0 -471
  198. package/tests/e2e/feedback-attachment.e2e.ts +0 -168
  199. package/tests/e2e/feedback-customer.e2e.ts +0 -226
  200. package/tests/e2e/feedback-management.e2e.ts +0 -565
  201. package/tests/e2e/feedback-submit.e2e.ts +0 -133
  202. package/tests/e2e/feedback-view.e2e.ts +0 -297
  203. package/tests/e2e/fixtures/test-data.ts +0 -235
  204. package/tests/e2e/health-check.e2e.ts +0 -230
  205. package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
  206. package/tests/e2e/helpers/test-utils.ts +0 -298
  207. package/tests/e2e/integration-placeholders.e2e.ts +0 -199
  208. package/tests/e2e/organization.e2e.ts +0 -292
  209. package/tests/e2e/permissions.e2e.ts +0 -424
  210. package/tests/e2e/project-widget.e2e.ts +0 -63
  211. package/tests/feedback/filters.test.ts +0 -29
  212. package/tests/hooks/use-permissions.test.ts +0 -52
  213. package/tests/lib/ai/classifier.test.ts +0 -104
  214. package/tests/lib/ai/duplicate-detector.test.ts +0 -234
  215. package/tests/lib/attachments-schema.test.ts +0 -30
  216. package/tests/lib/auth/session.test.ts +0 -49
  217. package/tests/lib/auth-client.test.ts +0 -37
  218. package/tests/lib/auth-config.test.ts +0 -26
  219. package/tests/lib/feedback-prefill.test.ts +0 -52
  220. package/tests/lib/feedback-processor.test.ts +0 -41
  221. package/tests/lib/feedback-schema.test.ts +0 -33
  222. package/tests/lib/file-validator.test.ts +0 -48
  223. package/tests/lib/get-feedback-by-id.test.ts +0 -37
  224. package/tests/lib/invitations.test.ts +0 -35
  225. package/tests/lib/login-schema.test.ts +0 -36
  226. package/tests/lib/org-context.test.ts +0 -95
  227. package/tests/lib/organization-access.test.ts +0 -44
  228. package/tests/lib/organization-member-role-schema.test.ts +0 -41
  229. package/tests/lib/permissions.test.ts +0 -88
  230. package/tests/lib/portal-analytics.test.ts +0 -25
  231. package/tests/lib/portal-contributors.test.ts +0 -25
  232. package/tests/lib/portal-copy.test.ts +0 -27
  233. package/tests/lib/portal-i18n.test.ts +0 -30
  234. package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
  235. package/tests/lib/portal-modules.test.ts +0 -25
  236. package/tests/lib/portal-seo.test.ts +0 -25
  237. package/tests/lib/portal-sharing.test.ts +0 -25
  238. package/tests/lib/portal-sorting.test.ts +0 -25
  239. package/tests/lib/portal-theme.test.ts +0 -25
  240. package/tests/lib/rate-limit.test.ts +0 -142
  241. package/tests/lib/resolve-locale.test.ts +0 -34
  242. package/tests/lib/services/backup.test.ts +0 -145
  243. package/tests/lib/user-organizations.test.ts +0 -42
  244. package/tests/lib/user-role-schema.test.ts +0 -33
  245. package/tests/lib/user-schema.test.ts +0 -25
  246. package/tests/setup.ts +0 -74
  247. package/vercel.json +0 -4
@@ -1,437 +0,0 @@
1
- # User Login Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Add a login page and form that authenticate via better-auth email/password, support “remember me” (30-day session), and redirect logged-in users to `/dashboard`.
6
-
7
- **Architecture:** Use better-auth built-in `/api/auth/sign-in/email` endpoint and `auth.api.getSession` for server-side redirect. Client form validates input with a shared Zod schema and submits JSON to the auth endpoint. Session duration is configured in `lib/auth/config.ts`.
8
-
9
- **Tech Stack:** Next.js App Router, React 19, TypeScript, Tailwind, better-auth, Zod, Bun.
10
-
11
- ---
12
-
13
- ### Task 1: Add login validation schema (TDD)
14
-
15
- **Files:**
16
- - Modify: `lib/validations/auth.ts`
17
- - Create: `tests/lib/login-schema.test.ts`
18
-
19
- **Step 1: Write the failing test**
20
-
21
- `tests/lib/login-schema.test.ts`
22
- ```ts
23
- import { describe, expect, it } from "bun:test";
24
- import { loginSchema } from "@/lib/validations/auth";
25
-
26
- describe("loginSchema", () => {
27
- it("rejects empty email and password", () => {
28
- const result = loginSchema.safeParse({ email: "", password: "" });
29
- expect(result.success).toBe(false);
30
- if (!result.success) {
31
- const messages = result.error.issues.map((issue) => issue.message);
32
- expect(messages).toContain("请输入邮箱地址");
33
- expect(messages).toContain("请输入密码");
34
- }
35
- });
36
-
37
- it("accepts valid email and password", () => {
38
- const result = loginSchema.safeParse({ email: "user@example.com", password: "Password123" });
39
- expect(result.success).toBe(true);
40
- });
41
- });
42
- ```
43
-
44
- **Step 2: Run test to verify it fails**
45
-
46
- Run: `bun test tests/lib/login-schema.test.ts`
47
- Expected: FAIL because `loginSchema` is not defined.
48
-
49
- **Step 3: Write minimal implementation**
50
-
51
- `lib/validations/auth.ts`
52
- ```ts
53
- export const loginSchema = z.object({
54
- email: z
55
- .string()
56
- .min(1, "请输入邮箱地址")
57
- .email("请输入有效的邮箱地址")
58
- .max(255)
59
- .toLowerCase(),
60
- password: z.string().min(1, "请输入密码"),
61
- rememberMe: z.boolean().optional(),
62
- });
63
-
64
- export type LoginInput = z.infer<typeof loginSchema>;
65
- ```
66
-
67
- **Step 4: Run test to verify it passes**
68
-
69
- Run: `bun test tests/lib/login-schema.test.ts`
70
- Expected: PASS.
71
-
72
- **Step 5: Commit**
73
-
74
- ```bash
75
- git add lib/validations/auth.ts tests/lib/login-schema.test.ts
76
- git commit -m "test: add login schema validation"
77
- ```
78
-
79
- ---
80
-
81
- ### Task 2: Configure session expiry for remember-me
82
-
83
- **Files:**
84
- - Modify: `lib/auth/config.ts`
85
-
86
- **Step 1: Write the failing test**
87
-
88
- Create a simple configuration expectation test.
89
-
90
- `tests/lib/auth-config.test.ts`
91
- ```ts
92
- import { describe, expect, it } from "bun:test";
93
- import { auth } from "@/lib/auth/config";
94
-
95
- describe("auth session config", () => {
96
- it("uses 30-day session expiry", () => {
97
- expect(auth.options.session?.expiresIn).toBe(60 * 60 * 24 * 30);
98
- });
99
- });
100
- ```
101
-
102
- **Step 2: Run test to verify it fails**
103
-
104
- Run: `bun test tests/lib/auth-config.test.ts`
105
- Expected: FAIL because `expiresIn` is undefined.
106
-
107
- **Step 3: Write minimal implementation**
108
-
109
- `lib/auth/config.ts`
110
- ```ts
111
- session: {
112
- expiresIn: 60 * 60 * 24 * 30,
113
- },
114
- ```
115
-
116
- **Step 4: Run test to verify it passes**
117
-
118
- Run: `bun test tests/lib/auth-config.test.ts`
119
- Expected: PASS.
120
-
121
- **Step 5: Commit**
122
-
123
- ```bash
124
- git add lib/auth/config.ts tests/lib/auth-config.test.ts
125
- git commit -m "feat: set remember-me session duration"
126
- ```
127
-
128
- ---
129
-
130
- ### Task 3: Create login page (server component)
131
-
132
- **Files:**
133
- - Create: `app/(auth)/login/page.tsx`
134
-
135
- **Step 1: Write the failing test**
136
-
137
- Create a basic render test to ensure the page uses the LoginForm component.
138
-
139
- `tests/app/login-page.test.ts`
140
- ```ts
141
- import { describe, expect, it } from "bun:test";
142
- import LoginPage from "@/app/(auth)/login/page";
143
-
144
- // This is a shallow check for export existence.
145
- // Actual redirect behavior is validated manually.
146
-
147
- describe("LoginPage", () => {
148
- it("is a function", () => {
149
- expect(typeof LoginPage).toBe("function");
150
- });
151
- });
152
- ```
153
-
154
- **Step 2: Run test to verify it fails**
155
-
156
- Run: `bun test tests/app/login-page.test.ts`
157
- Expected: FAIL because the file does not exist.
158
-
159
- **Step 3: Write minimal implementation**
160
-
161
- `app/(auth)/login/page.tsx`
162
- ```tsx
163
- import { headers } from "next/headers";
164
- import { redirect } from "next/navigation";
165
- import { LoginForm } from "@/components/auth/login-form";
166
- import { auth } from "@/lib/auth/config";
167
-
168
- export default async function LoginPage() {
169
- const session = await auth.api.getSession({ headers: await headers() });
170
-
171
- if (session) {
172
- redirect("/dashboard");
173
- }
174
-
175
- return (
176
- <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 px-4 py-12">
177
- <div className="mx-auto flex w-full max-w-md flex-col gap-6">
178
- <div className="text-center">
179
- <h1 className="text-3xl font-semibold tracking-tight text-slate-900">
180
- Echo
181
- </h1>
182
- <p className="mt-2 text-sm text-slate-600">
183
- 登录以继续访问你的反馈和组织
184
- </p>
185
- </div>
186
- <LoginForm />
187
- </div>
188
- </div>
189
- );
190
- }
191
- ```
192
-
193
- **Step 4: Run test to verify it passes**
194
-
195
- Run: `bun test tests/app/login-page.test.ts`
196
- Expected: PASS.
197
-
198
- **Step 5: Commit**
199
-
200
- ```bash
201
- git add app/(auth)/login/page.tsx tests/app/login-page.test.ts
202
- git commit -m "feat: add login page"
203
- ```
204
-
205
- ---
206
-
207
- ### Task 4: Create login form (client component)
208
-
209
- **Files:**
210
- - Create: `components/auth/login-form.tsx`
211
-
212
- **Step 1: Write the failing test**
213
-
214
- Test validation behavior and error messaging without a DOM by using the shared schema.
215
-
216
- `tests/components/login-form-validation.test.ts`
217
- ```ts
218
- import { describe, expect, it } from "bun:test";
219
- import { loginSchema } from "@/lib/validations/auth";
220
-
221
- describe("LoginForm validation", () => {
222
- it("rejects invalid email format", () => {
223
- const result = loginSchema.safeParse({ email: "invalid", password: "pass" });
224
- expect(result.success).toBe(false);
225
- });
226
- });
227
- ```
228
-
229
- **Step 2: Run test to verify it fails**
230
-
231
- Run: `bun test tests/components/login-form-validation.test.ts`
232
- Expected: FAIL because login form/schema not yet wired (schema exists but test file missing).
233
-
234
- **Step 3: Write minimal implementation**
235
-
236
- `components/auth/login-form.tsx`
237
- ```tsx
238
- "use client";
239
-
240
- import { useState } from "react";
241
- import { useRouter } from "next/navigation";
242
- import { Button } from "@/components/ui/button";
243
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
244
- import { Checkbox } from "@/components/ui/checkbox";
245
- import { Input } from "@/components/ui/input";
246
- import { Label } from "@/components/ui/label";
247
- import { loginSchema, type LoginInput } from "@/lib/validations/auth";
248
-
249
- export function LoginForm() {
250
- const router = useRouter();
251
- const [isLoading, setIsLoading] = useState(false);
252
- const [formError, setFormError] = useState<string | null>(null);
253
- const [errors, setErrors] = useState<Record<string, string>>({});
254
- const [formData, setFormData] = useState<LoginInput>({
255
- email: "",
256
- password: "",
257
- rememberMe: false,
258
- });
259
-
260
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
261
- const { name, value, type, checked } = e.target;
262
- setFormData((prev) => ({
263
- ...prev,
264
- [name]: type === "checkbox" ? checked : value,
265
- }));
266
- setErrors((prev) => ({ ...prev, [name]: "" }));
267
- setFormError(null);
268
- };
269
-
270
- const validateForm = () => {
271
- const result = loginSchema.safeParse(formData);
272
- if (result.success) {
273
- setErrors({});
274
- return true;
275
- }
276
- const nextErrors: Record<string, string> = {};
277
- for (const issue of result.error.issues) {
278
- const key = issue.path?.[0];
279
- if (typeof key === "string") {
280
- nextErrors[key] = issue.message;
281
- }
282
- }
283
- setErrors(nextErrors);
284
- return false;
285
- };
286
-
287
- const handleSubmit = async (event: React.FormEvent) => {
288
- event.preventDefault();
289
- setFormError(null);
290
-
291
- if (!validateForm()) {
292
- return;
293
- }
294
-
295
- setIsLoading(true);
296
-
297
- try {
298
- const res = await fetch("/api/auth/sign-in/email", {
299
- method: "POST",
300
- headers: { "Content-Type": "application/json" },
301
- body: JSON.stringify({
302
- email: formData.email,
303
- password: formData.password,
304
- rememberMe: formData.rememberMe,
305
- }),
306
- });
307
-
308
- const json = await res.json();
309
-
310
- if (!res.ok) {
311
- throw new Error(json?.message ?? "邮箱或密码错误");
312
- }
313
-
314
- router.push("/dashboard");
315
- } catch {
316
- setFormError("邮箱或密码错误");
317
- } finally {
318
- setIsLoading(false);
319
- }
320
- };
321
-
322
- return (
323
- <Card>
324
- <CardHeader>
325
- <CardTitle>登录</CardTitle>
326
- <CardDescription>输入邮箱和密码登录</CardDescription>
327
- </CardHeader>
328
- <CardContent>
329
- <form onSubmit={handleSubmit} className="space-y-4">
330
- {formError ? (
331
- <div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
332
- {formError}
333
- </div>
334
- ) : null}
335
-
336
- <div className="space-y-2">
337
- <Label htmlFor="email">邮箱</Label>
338
- <Input
339
- id="email"
340
- name="email"
341
- type="email"
342
- placeholder="you@example.com"
343
- value={formData.email}
344
- onChange={handleChange}
345
- disabled={isLoading}
346
- className={errors.email ? "border-destructive" : ""}
347
- />
348
- {errors.email ? (
349
- <p className="text-sm text-destructive">{errors.email}</p>
350
- ) : null}
351
- </div>
352
-
353
- <div className="space-y-2">
354
- <Label htmlFor="password">密码</Label>
355
- <Input
356
- id="password"
357
- name="password"
358
- type="password"
359
- value={formData.password}
360
- onChange={handleChange}
361
- disabled={isLoading}
362
- className={errors.password ? "border-destructive" : ""}
363
- />
364
- {errors.password ? (
365
- <p className="text-sm text-destructive">{errors.password}</p>
366
- ) : null}
367
- </div>
368
-
369
- <div className="flex items-center justify-between">
370
- <div className="flex items-center space-x-2">
371
- <Checkbox
372
- id="rememberMe"
373
- name="rememberMe"
374
- checked={formData.rememberMe}
375
- onCheckedChange={(checked) =>
376
- setFormData((prev) => ({ ...prev, rememberMe: checked === true }))
377
- }
378
- disabled={isLoading}
379
- />
380
- <Label htmlFor="rememberMe" className="text-sm font-normal">
381
- 记住我
382
- </Label>
383
- </div>
384
- <a className="text-sm text-primary hover:underline" href="/forgot-password">
385
- 忘记密码?
386
- </a>
387
- </div>
388
-
389
- <Button type="submit" className="w-full" disabled={isLoading}>
390
- {isLoading ? "登录中..." : "登录"}
391
- </Button>
392
-
393
- <p className="text-center text-sm text-muted-foreground">
394
- 还没有账户?
395
- <a className="ml-1 text-primary hover:underline" href="/register">
396
- 注册
397
- </a>
398
- </p>
399
- </form>
400
- </CardContent>
401
- </Card>
402
- );
403
- }
404
- ```
405
-
406
- **Step 4: Run test to verify it passes**
407
-
408
- Run: `bun test tests/components/login-form-validation.test.ts`
409
- Expected: PASS.
410
-
411
- **Step 5: Commit**
412
-
413
- ```bash
414
- git add components/auth/login-form.tsx tests/components/login-form-validation.test.ts
415
- git commit -m "feat: add login form"
416
- ```
417
-
418
- ---
419
-
420
- ### Task 5: Full verification
421
-
422
- **Step 1: Run lint**
423
-
424
- Run: `bun run lint`
425
- Expected: PASS.
426
-
427
- **Step 2: Run full test suite**
428
-
429
- Run: `bun test`
430
- Expected: PASS.
431
-
432
- **Step 3: Commit (if any changes)**
433
-
434
- ```bash
435
- git add -A
436
- git commit -m "chore: verify login flow"
437
- ```
@@ -1,47 +0,0 @@
1
- # 用户注册设计说明
2
-
3
- **日期:** 2026-01-02
4
- **范围:** Story 4.1 用户注册(认证域 + 业务域)
5
-
6
- ## 目标
7
- 为新用户提供注册入口,完成账号创建、默认组织创建与自动登录,并保持认证域与业务域解耦。
8
-
9
- ## 架构与边界
10
- - **认证域(better-auth)**:用户/会话等授权表由 better-auth 的 Drizzle schema 提供与维护。
11
- - **业务域(自有表)**:组织与组织成员表独立维护;新增 `user_profiles` 保存姓名等扩展信息。
12
- - **迁移策略**:统一通过 drizzle-kit 生成并运行迁移(不手写 SQL)。
13
-
14
- ## 数据模型
15
- - `auth_*`(better-auth 提供):用户、会话等表结构由其 Drizzle schema 生成。
16
- - `user_profiles`
17
- - `userId` 外键指向认证域用户表
18
- - `name`(必填)及未来可扩展字段
19
- - `organizations`
20
- - `name`, `slug`
21
- - `organization_members`
22
- - `organizationId`, `userId`, `role`(注册时为 `admin`)
23
-
24
- ## 注册流程
25
- 1. 客户端提交姓名/邮箱/密码到 `POST /api/auth/register`。
26
- 2. 服务端使用 Zod 校验(邮箱格式/密码强度/姓名)。
27
- 3. 调用 better-auth 注册接口创建用户并建立会话。
28
- 4. 同一事务内创建 `user_profiles`、默认组织与成员关系。
29
- 5. 成功返回 201,前端跳转 `/dashboard`。
30
-
31
- ## 错误处理
32
- - 校验失败:400 + 字段级错误。
33
- - 邮箱已存在:409。
34
- - 其他错误:500(通用错误提示)。
35
-
36
- ## 前端
37
- - 页面:`app/(auth)/register/page.tsx`
38
- - 表单组件:`components/auth/register-form.tsx`
39
- - 客户端校验 + 服务端再次校验
40
- - 成功后提示 toast 并跳转仪表板
41
-
42
- ## 测试范围
43
- - API:成功注册、邮箱重复、弱密码/无效邮箱。
44
- - 验证创建了 profile、组织与成员关系。
45
-
46
- ## 待确认
47
- - better-auth Drizzle schema 的具体导出与命名(需以官方文档为准)。