@nexttylabs/echo 0.3.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 (248) hide show
  1. package/CHANGELOG.md +25 -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/app/api/internal/domain-lookup/route.ts +0 -67
  27. package/bun.lock +0 -2503
  28. package/components/portal/project-switcher.tsx +0 -20
  29. package/docker-compose.dev.yml +0 -26
  30. package/docker-compose.yml +0 -98
  31. package/docs/architecture.md +0 -259
  32. package/docs/component-inventory.md +0 -261
  33. package/docs/database-migrations.md +0 -76
  34. package/docs/development-guide.md +0 -209
  35. package/docs/e2e-user-flows.csv +0 -31
  36. package/docs/er-diagram-feedback.mmd +0 -138
  37. package/docs/er-diagram.mmd +0 -281
  38. package/docs/i18n-check-report.md +0 -296
  39. package/docs/index.md +0 -214
  40. package/docs/logic-chain.md +0 -94
  41. package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
  42. package/docs/plans/2026-01-02-user-login-design.md +0 -37
  43. package/docs/plans/2026-01-02-user-login.md +0 -437
  44. package/docs/plans/2026-01-02-user-registration-design.md +0 -47
  45. package/docs/plans/2026-01-02-user-registration.md +0 -628
  46. package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
  47. package/docs/plans/2026-01-03-roles-permissions.md +0 -266
  48. package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
  49. package/docs/plans/2026-01-05-member-removal.md +0 -186
  50. package/docs/plans/2026-01-05-organization-creation.md +0 -374
  51. package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
  52. package/docs/plans/2026-01-05-role-configuration.md +0 -441
  53. package/docs/plans/2026-01-06-file-upload-support.md +0 -804
  54. package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
  55. package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
  56. package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
  57. package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
  58. package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
  59. package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
  60. package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
  61. package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
  62. package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
  63. package/docs/plans/2026-01-09-settings-center-design.md +0 -114
  64. package/docs/plans/2026-01-09-settings-center.md +0 -948
  65. package/docs/plans/2026-01-10-organization-only-design.md +0 -66
  66. package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
  67. package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
  68. package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
  69. package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
  70. package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
  71. package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
  72. package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
  73. package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
  74. package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
  75. package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
  76. package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
  77. package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
  78. package/docs/plans/2026-01-18-changesets-design.md +0 -40
  79. package/docs/product_changes.md +0 -37
  80. package/docs/project-overview.md +0 -159
  81. package/docs/project-scan-report.json +0 -104
  82. package/docs/route-role-visibility.md +0 -51
  83. package/docs/source-tree-analysis.md +0 -150
  84. package/docs/testing/delete-project-manual-tests.md +0 -18
  85. package/docs/user-story-tracking.md +0 -191
  86. package/eslint.config.mjs +0 -19
  87. package/lib/db/migrations/.gitkeep +0 -0
  88. package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
  89. package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
  90. package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
  91. package/lib/db/migrations/0003_add_org_description.sql +0 -1
  92. package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
  93. package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
  94. package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
  95. package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
  96. package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
  97. package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
  98. package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
  99. package/lib/db/migrations/0010_kind_junta.sql +0 -8
  100. package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
  101. package/lib/db/migrations/0012_giant_power_man.sql +0 -24
  102. package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
  103. package/lib/db/migrations/0014_blue_alice.sql +0 -18
  104. package/lib/db/migrations/0015_webhook_tables.sql +0 -41
  105. package/lib/db/migrations/0016_github_integration.sql +0 -30
  106. package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
  107. package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
  108. package/lib/db/migrations/0018_same_spitfire.sql +0 -1
  109. package/lib/db/migrations/0019_jittery_loners.sql +0 -16
  110. package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
  111. package/lib/db/migrations/meta/0001_snapshot.json +0 -553
  112. package/lib/db/migrations/meta/0002_snapshot.json +0 -560
  113. package/lib/db/migrations/meta/0003_snapshot.json +0 -650
  114. package/lib/db/migrations/meta/0004_snapshot.json +0 -852
  115. package/lib/db/migrations/meta/0005_snapshot.json +0 -900
  116. package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
  117. package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
  118. package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
  119. package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
  120. package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
  121. package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
  122. package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
  123. package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
  124. package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
  125. package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
  126. package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
  127. package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
  128. package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
  129. package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
  130. package/lib/db/schema/projects.ts +0 -145
  131. package/lib/db/schema/user-profiles.ts +0 -31
  132. package/lib/validations/projects.ts +0 -49
  133. package/next-env.d.ts +0 -6
  134. package/playwright.config.ts +0 -44
  135. package/proxy.test.ts +0 -131
  136. package/proxy.ts +0 -190
  137. package/scripts/backup-db.sh +0 -57
  138. package/scripts/backup-db.ts +0 -24
  139. package/scripts/generate-openapi.ts +0 -22
  140. package/scripts/migration-helper.ts +0 -39
  141. package/scripts/pre-deploy.ts +0 -75
  142. package/scripts/restore-db.sh +0 -60
  143. package/scripts/rollback.ts +0 -72
  144. package/scripts/seed-tags.ts +0 -48
  145. package/tests/api/feedback-bulk.test.ts +0 -47
  146. package/tests/api/feedback-by-id.test.ts +0 -67
  147. package/tests/api/feedback-comments-route-import.test.ts +0 -26
  148. package/tests/api/feedback-create.test.ts +0 -71
  149. package/tests/api/feedback-delete.test.ts +0 -160
  150. package/tests/api/feedback-filter.test.ts +0 -250
  151. package/tests/api/feedback-list.test.ts +0 -234
  152. package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
  153. package/tests/api/feedback-similar.test.ts +0 -46
  154. package/tests/api/feedback-sort.test.ts +0 -261
  155. package/tests/api/feedback-status-enum.test.ts +0 -49
  156. package/tests/api/feedback-status-filter.test.ts +0 -117
  157. package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
  158. package/tests/api/feedback.test.ts +0 -175
  159. package/tests/api/identify-jwt.test.ts +0 -25
  160. package/tests/api/invitation-accept.test.ts +0 -213
  161. package/tests/api/organization-invitations.test.ts +0 -186
  162. package/tests/api/organization-members-list.test.ts +0 -79
  163. package/tests/api/organization-members.test.ts +0 -340
  164. package/tests/api/organizations.test.ts +0 -149
  165. package/tests/api/register.test.ts +0 -112
  166. package/tests/api/upload.test.ts +0 -103
  167. package/tests/api/vote.test.ts +0 -82
  168. package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
  169. package/tests/app/admin-feedback-list-page.test.tsx +0 -25
  170. package/tests/app/admin-feedback-new-page.test.tsx +0 -25
  171. package/tests/app/health-route-helpers.test.ts +0 -27
  172. package/tests/app/login-page.test.ts +0 -26
  173. package/tests/app/portal-page.test.ts +0 -29
  174. package/tests/app/project-portal-overview.test.tsx +0 -25
  175. package/tests/app/widget-page-import.test.ts +0 -25
  176. package/tests/components/create-post-dialog-defaults.test.ts +0 -43
  177. package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
  178. package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
  179. package/tests/components/feedback/feedback-detail.test.tsx +0 -25
  180. package/tests/components/feedback/feedback-stats.test.tsx +0 -49
  181. package/tests/components/feedback-bulk-actions.test.tsx +0 -39
  182. package/tests/components/feedback-i18n-keys.test.ts +0 -70
  183. package/tests/components/feedback-list-controls-compile.test.ts +0 -25
  184. package/tests/components/feedback-list-controls.test.tsx +0 -204
  185. package/tests/components/feedback-list-item.test.tsx +0 -67
  186. package/tests/components/landing/hero.test.tsx +0 -46
  187. package/tests/components/layout/language-switcher.test.tsx +0 -25
  188. package/tests/components/layout/sidebar.test.tsx +0 -157
  189. package/tests/components/login-form.test.ts +0 -25
  190. package/tests/components/organization-form.test.ts +0 -32
  191. package/tests/components/organization-switcher.test.ts +0 -25
  192. package/tests/components/pagination.test.tsx +0 -43
  193. package/tests/components/portal-overview.test.tsx +0 -25
  194. package/tests/components/profile-form.test.tsx +0 -139
  195. package/tests/components/role-selector.test.ts +0 -31
  196. package/tests/components/status-chart.test.tsx +0 -90
  197. package/tests/e2e/auth.e2e.ts +0 -323
  198. package/tests/e2e/feedback-actions.e2e.ts +0 -471
  199. package/tests/e2e/feedback-attachment.e2e.ts +0 -168
  200. package/tests/e2e/feedback-customer.e2e.ts +0 -226
  201. package/tests/e2e/feedback-management.e2e.ts +0 -565
  202. package/tests/e2e/feedback-submit.e2e.ts +0 -133
  203. package/tests/e2e/feedback-view.e2e.ts +0 -297
  204. package/tests/e2e/fixtures/test-data.ts +0 -235
  205. package/tests/e2e/health-check.e2e.ts +0 -230
  206. package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
  207. package/tests/e2e/helpers/test-utils.ts +0 -298
  208. package/tests/e2e/integration-placeholders.e2e.ts +0 -199
  209. package/tests/e2e/organization.e2e.ts +0 -292
  210. package/tests/e2e/permissions.e2e.ts +0 -424
  211. package/tests/e2e/project-widget.e2e.ts +0 -63
  212. package/tests/feedback/filters.test.ts +0 -29
  213. package/tests/hooks/use-permissions.test.ts +0 -52
  214. package/tests/lib/ai/classifier.test.ts +0 -104
  215. package/tests/lib/ai/duplicate-detector.test.ts +0 -234
  216. package/tests/lib/attachments-schema.test.ts +0 -30
  217. package/tests/lib/auth/session.test.ts +0 -49
  218. package/tests/lib/auth-client.test.ts +0 -37
  219. package/tests/lib/auth-config.test.ts +0 -26
  220. package/tests/lib/feedback-prefill.test.ts +0 -52
  221. package/tests/lib/feedback-processor.test.ts +0 -41
  222. package/tests/lib/feedback-schema.test.ts +0 -33
  223. package/tests/lib/file-validator.test.ts +0 -48
  224. package/tests/lib/get-feedback-by-id.test.ts +0 -37
  225. package/tests/lib/invitations.test.ts +0 -35
  226. package/tests/lib/login-schema.test.ts +0 -36
  227. package/tests/lib/org-context.test.ts +0 -95
  228. package/tests/lib/organization-access.test.ts +0 -44
  229. package/tests/lib/organization-member-role-schema.test.ts +0 -41
  230. package/tests/lib/permissions.test.ts +0 -88
  231. package/tests/lib/portal-analytics.test.ts +0 -25
  232. package/tests/lib/portal-contributors.test.ts +0 -25
  233. package/tests/lib/portal-copy.test.ts +0 -27
  234. package/tests/lib/portal-i18n.test.ts +0 -30
  235. package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
  236. package/tests/lib/portal-modules.test.ts +0 -25
  237. package/tests/lib/portal-seo.test.ts +0 -25
  238. package/tests/lib/portal-sharing.test.ts +0 -25
  239. package/tests/lib/portal-sorting.test.ts +0 -25
  240. package/tests/lib/portal-theme.test.ts +0 -25
  241. package/tests/lib/rate-limit.test.ts +0 -142
  242. package/tests/lib/resolve-locale.test.ts +0 -34
  243. package/tests/lib/services/backup.test.ts +0 -145
  244. package/tests/lib/user-organizations.test.ts +0 -42
  245. package/tests/lib/user-role-schema.test.ts +0 -33
  246. package/tests/lib/user-schema.test.ts +0 -25
  247. package/tests/setup.ts +0 -74
  248. 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 的具体导出与命名(需以官方文档为准)。