diff --git a/docs/superpowers/specs/2026-05-15-register-page-improvement-design.md b/docs/superpowers/specs/2026-05-15-register-page-improvement-design.md new file mode 100644 index 0000000..40815c5 --- /dev/null +++ b/docs/superpowers/specs/2026-05-15-register-page-improvement-design.md @@ -0,0 +1,184 @@ +# Registration Page Improvement Design + +**Date**: 2026-05-15 +**Branch**: feat/registration-page +**Status**: draft + +## Scope + +Full-stack improvements to the registration page covering UI/UX, engineering quality, and visual polish. The existing `register.vue` (146 lines) works correctly but has accumulated technical debt and UX gaps. + +## Architecture: Component Decomposition (Approach B) + +Split the monolithic `register.vue` into focused components with clear data flow. + +### File Structure + +``` +app/pages/register.vue # Page shell (~60 lines) +app/components/register/ + RegisterForm.vue # Main form with submission logic (~120 lines) + PasswordInput.vue # Password field with show/hide toggle (~40 lines) + PasswordStrength.vue # Real-time strength indicator (~35 lines) + CaptchaField.vue # Captcha SVG + refresh + input (~50 lines) +packages/shared/auth-schema.ts # Shared Zod schema (client + server) +``` + +### Data Flow + +``` +register.vue + │ Owns: captchaToken, captchaSvg, toast + │ Provides: fetchCaptcha() + │ + └─ RegisterForm + │ Props: captchaSvg, captchaToken + │ Emits: refresh-captcha, success, error + │ Uses: useHttpFetch → POST /api/auth/register + │ Internal: loading state + │ + ├─ PasswordInput (x2: password + confirmPassword) + │ Props: modelValue, label, placeholder, disabled + │ Emits: update:modelValue + │ Internal: showPassword toggle + │ + ├─ PasswordStrength + │ Props: password (computed from form values) + │ Output: score 0-4, label text, color-coded bar + │ + └─ CaptchaField + Props: svg (v-html), loading + Emits: refresh +``` + +## Component Specifications + +### PasswordInput + +| Prop | Type | Default | Purpose | +|------|------|---------|---------| +| modelValue | string | required | v-model binding | +| label | string | "密码" | Field label | +| placeholder | string | "请输入密码" | Placeholder | +| disabled | boolean | false | Disable during submission | + +| Emit | Payload | Purpose | +|------|---------|---------| +| update:modelValue | string | v-model update | + +States: default, showing password, focused (ring), error (red border + message), disabled. + +### PasswordStrength + +| Prop | Type | Purpose | +|------|------|---------| +| password | string | Password to evaluate | + +Scoring algorithm: +- **Empty**: Nothing rendered +- **Weak (1)**: Only digits or only letters, < 8 chars — red 1/4 bar +- **Fair (2)**: Letters + digits, >= 8 chars — amber 2/4 bar +- **Strong (3)**: Upper + lower + digits, >= 10 chars — green 3/4 bar +- **Very Strong (4)**: Upper + lower + digits + special chars, >= 12 chars — emerald 4/4 bar + +### CaptchaField + +| Prop | Type | Purpose | +|------|------|---------| +| svg | string | SVG markup rendered via v-html | +| loading | boolean | Show spinner, disable input during refresh | + +| Emit | Payload | Purpose | +|------|---------|---------| +| refresh | none | Request new captcha | + +States: default (SVG + input + refresh button), refreshing (spinner + dimmed), load error (error message + retry link). + +### RegisterForm Lifecycle + +1. **Idle**: Form editable, submit button enabled +2. **Validating**: Client-side Zod via UForm, field-level errors +3. **Submitting**: Button shows loading spinner + "注册中...", all inputs/buttons disabled +4. **Failure**: Field-level errors (via setFieldError) or generic toast +5. **Success**: Card content replaced with success state (check icon + "注册成功" + "去登录" link) + +## Schema Deduplication + +Extract the Zod schema from both `register.vue` (client inline) and `server/utils/auth/validation.ts` into a single shared module: + +``` +packages/shared/auth-schema.ts + exports: registerSchema + used by: client (UForm validation), server (safeParse) +``` + +## HTTP Client Consistency + +Replace raw `$fetch` in register.vue with `useHttpFetch` from `app/utils/http/factory.ts`, matching the pattern used elsewhere in the app. The `useHttpFetch` composable auto-unwraps the `{ code, message, data }` envelope. + +## Error Handling + +### Response Format Extension + +Existing envelope unchanged. Add optional `field` in `data` for field-level errors: + +```json +// Username conflict → field-level +{ "code": 1, "message": "用户名已存在", "data": { "field": "username" } } + +// Captcha wrong → field-level +{ "code": 1, "message": "验证码错误", "data": { "field": "captchaText" } } + +// Captcha expired → field-level +{ "code": 1, "message": "验证码已过期", "data": { "field": "captchaText" } } + +// Unknown → generic toast +{ "code": 1, "message": "服务器内部错误", "data": null } +``` + +### Error Layers + +| Layer | Scenario | Handling | UI | +|-------|----------|----------|-----| +| Client validation | Format, length, mismatch | UForm + Zod, no request | Field-level red text | +| Server business | Duplicate username, wrong captcha | API returns field code | setFieldError or toast | +| Network | Timeout, offline, 500 | try/catch on useHttpFetch | Toast "网络异常,请稍后重试" | +| Mount failure | Captcha load fails | try/catch in onMounted | "加载失败,点此重试" in captcha area | + +## Success Flow + +After successful registration, instead of navigating to `/login?registered=1` (which does not exist yet), show an inline success state within the card: +- Green check icon +- "注册成功" heading +- Link/button: "去登录" + +The existing redirect behavior is kept as a fallback once the login page exists. + +## Testing Strategy + +### Unit Tests (Vitest) +- `auth-schema.ts`: Zod validation pass/fail for various inputs +- `PasswordStrength`: Scoring logic for each strength level +- `server/utils/auth/validation.ts`: Server-side schema validation + +### Component Tests (@nuxt/test-utils + @vue/test-utils) +- `PasswordInput`: Show/hide toggle, v-model binding, disabled state +- `PasswordStrength`: Renders correct color and label for each password input +- `CaptchaField`: SVG rendering, refresh emit, loading state +- `RegisterForm`: Form validation, submission flow, disabled state during loading + +## Visual Polish + +- Optimize card vertical centering and form field spacing +- Softer card shadow and border radius +- Add "已有账号?去登录" + home page links below the form +- Captcha refresh button shows rotation animation during loading + +## What's NOT Included + +- Login page (separate feature) +- Session/JWT creation on registration (separate feature) +- Email verification (separate feature) +- Social login (separate feature) +- Rate limiting (separate feature, needs infrastructure decisions) +- Captcha storage migration from in-memory Map (low traffic, Map is sufficient for now)