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