Skip to content

♻️ Refactor login route into FSD structure#54

Merged
nbsp1221 merged 2 commits intomainfrom
feat/refactor-login-fsd
Sep 30, 2025
Merged

♻️ Refactor login route into FSD structure#54
nbsp1221 merged 2 commits intomainfrom
feat/refactor-login-fsd

Conversation

@nbsp1221
Copy link
Owner

@nbsp1221 nbsp1221 commented Sep 30, 2025

Summary

Refactors the login page to follow Feature-Sliced Design (FSD) architecture, bringing it in line with the existing FSD patterns used in other pages (home, add-videos, player).

Changes

Architecture Refactoring

  • Before: Monolithic login route component (151 lines)
  • After: Layered FSD structure with clear separation of concerns

New File Structure

app/pages/login/ui/LoginPage.tsx            (10 lines)  - Page composition layer
app/widgets/login-view/model/useLoginView.ts (93 lines)  - Business logic hook
app/widgets/login-view/ui/LoginView.tsx     (112 lines) - Pure presentation
app/routes/login.tsx                        (11 lines)  - Thin route layer

Key Improvements

  • Separation of Concerns: Logic, presentation, and composition clearly separated
  • Testability: Business logic extracted into useLoginView hook (unit testable)
  • Reusability: LoginView component can be used independently
  • Consistency: Matches existing FSD patterns across the project
  • Maintainability: Each layer has single responsibility

Feature Parity

All existing functionality preserved:

  • ✅ Email/password form inputs
  • ✅ Password visibility toggle
  • ✅ Form validation (empty field checks)
  • ✅ Login API integration
  • ✅ Error message display
  • ✅ Loading states
  • ✅ Post-login redirection
  • redirectTo query parameter support
  • ✅ Auto-redirect for authenticated users

Testing

Quality Checks

  • bun run lint:fix - No new linting errors
  • bun run typecheck - All TypeScript types valid
  • bun run test - All 431 tests passing
  • bun run build - Production build successful

E2E Testing (Playwright)

  • Successful Login: Valid credentials → redirects to home page
  • Logout Flow: Logout → redirects back to login page
  • Invalid Credentials: Shows error alert "Invalid email or password"
  • Form State: Input values preserved on error

Related

Part of the ongoing FSD architecture migration:


Ready to merge - All checks passed, E2E tested, fully backward compatible.

Summary by CodeRabbit

  • New Features

    • Streamlined Login page with centered UI, email/password form, client-side validation and error alerts.
    • Show/hide password toggle; inputs and submit disabled during loading.
    • Redirects after successful login (supports redirectTo) and auto-redirects authenticated users away from login.
  • Refactor

    • Simplified login route to delegate rendering to the new Login page while preserving page metadata.

Restructures the login page following Feature-Sliced Design (FSD) architecture for improved maintainability and testability.

Changes:
- Split monolithic login route (151 lines) into layered FSD structure
- Created LoginPage component in pages layer for composition
- Extracted business logic to useLoginView hook in widgets/login-view/model
- Created pure presentation LoginView component in widgets/login-view/ui
- Maintained 100% feature parity with existing implementation
- All functionality preserved: authentication, redirects, error handling, password visibility toggle

Architecture improvements:
- Clear separation of concerns (presentation, logic, composition)
- Testable hook-based business logic
- Reusable LoginView widget
- Consistent with existing FSD patterns (home, add-videos, player)

Verified:
- TypeScript type checking passes
- All 431 unit tests pass
- Production build succeeds
- E2E tests confirm correct behavior (login, logout, error handling)
@coderabbitai
Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds a presentational LoginView and a useLoginView hook, introduces a LoginPage that composes them, and simplifies the login route to render LoginPage and export meta as a MetaFunction. Client-side state, validation, submit handling, and redirect logic moved into the hook.

Changes

Cohort / File(s) Summary of modifications
Page wrapper & route
app/pages/login/ui/LoginPage.tsx, app/routes/login.tsx
New LoginPage component that consumes useLoginView and renders LoginView. Route simplified to LoginRoute returning <LoginPage />; meta exported as MetaFunction. Removed in-route auth/form logic.
Login view — model (hook)
app/widgets/login-view/model/useLoginView.ts
New useLoginView hook and UseLoginViewResult interface: manages email/password, showPassword, loading, error; handlers for input changes, toggle visibility, submit; invokes auth store login and navigates on authenticated state or successful login; maps and exposes errors.
Login view — UI
app/widgets/login-view/ui/LoginView.tsx
New LoginView component and LoginViewProps interface: presentational form with email & password inputs, password visibility toggle, error alert, and submit button; controlled by props and respects loading/disabled states.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant Route as LoginRoute
  participant Page as LoginPage
  participant Hook as useLoginView
  participant UI as LoginView
  participant Auth as AuthStore
  participant Nav as Router/Navigate

  U->>Route: GET /login?redirectTo=...
  Route->>Page: render LoginPage
  Page->>Hook: initialize (state, handlers)
  Hook->>Nav: if already authenticated -> navigate(redirectTo or "/")
  Page->>UI: render with props from Hook

  U->>UI: fills form & submits
  UI->>Hook: onSubmit(email, password)
  Hook->>Hook: validate, set loading
  Hook->>Auth: login(email, password)
  alt success
    Auth-->>Hook: auth success
    Hook->>Nav: navigate(redirectTo or "/")
  else failure
    Auth-->>Hook: error
    Hook->>Hook: set error message
  end
  Hook->>Hook: set loading = false
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paw: a route made lean,
A hook now tends the login scene.
Buttons bright and passwords hid,
Errors chased — then off I skid.
Hop, submit, the carrots win! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary change by indicating the login route has been refactored into the project’s FSD structure, making it clear, specific, and closely aligned with the PR objectives.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refactor-login-fsd

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6d8f9b and 0a65172.

📒 Files selected for processing (4)
  • app/pages/login/ui/LoginPage.tsx (1 hunks)
  • app/routes/login.tsx (1 hunks)
  • app/widgets/login-view/model/useLoginView.ts (1 hunks)
  • app/widgets/login-view/ui/LoginView.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript에서 any를 사용하지 않고 모든 함수에 포괄적인 타입 힌트를 제공한다 (strict TS)
로그에 비밀값을 남기지 않도록 모든 콘솔 출력은 민감정보를 마스킹/제거한다

**/*.{ts,tsx}: Use the ~/* path alias (from tsconfig.json) for internal imports
Prefer explicit interfaces when defining object shapes in TypeScript (strict mode)
Avoid using any in TypeScript (project runs in strict mode)
Use two-space indentation throughout TypeScript/TSX files
Name components, hooks, and providers with PascalCase
Name helpers and state setters with camelCase
File names should mirror their primary export

Files:

  • app/widgets/login-view/ui/LoginView.tsx
  • app/widgets/login-view/model/useLoginView.ts
  • app/routes/login.tsx
  • app/pages/login/ui/LoginPage.tsx
**/*.{ts,tsx,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

코드 주석과 문서는 영어로 작성한다

Files:

  • app/widgets/login-view/ui/LoginView.tsx
  • app/widgets/login-view/model/useLoginView.ts
  • app/routes/login.tsx
  • app/pages/login/ui/LoginPage.tsx
app/**

📄 CodeRabbit inference engine (AGENTS.md)

Place all React Router app source under app/ following the repository’s feature-sliced layout

Files:

  • app/widgets/login-view/ui/LoginView.tsx
  • app/widgets/login-view/model/useLoginView.ts
  • app/routes/login.tsx
  • app/pages/login/ui/LoginPage.tsx
app/widgets/**

📄 CodeRabbit inference engine (AGENTS.md)

Put UI compositions in app/widgets/

Files:

  • app/widgets/login-view/ui/LoginView.tsx
  • app/widgets/login-view/model/useLoginView.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Split multiline JSX props across lines per Stylistic ESLint rules

Files:

  • app/widgets/login-view/ui/LoginView.tsx
  • app/routes/login.tsx
  • app/pages/login/ui/LoginPage.tsx
app/pages/**

📄 CodeRabbit inference engine (AGENTS.md)

app/pages/**: Keep route shells in app/pages/ (pages expose route shells only)
Keep routes thin in app/pages/ (delegate business logic to modules/hooks)

Files:

  • app/pages/login/ui/LoginPage.tsx
🧬 Code graph analysis (4)
app/widgets/login-view/ui/LoginView.tsx (5)
app/components/ui/card.tsx (5)
  • Card (78-78)
  • CardHeader (78-78)
  • CardTitle (78-78)
  • CardDescription (78-78)
  • CardContent (78-78)
app/components/ui/alert.tsx (2)
  • Alert (66-66)
  • AlertDescription (66-66)
app/components/ui/label.tsx (1)
  • Label (22-22)
app/components/ui/input.tsx (1)
  • Input (21-21)
app/components/ui/button.tsx (1)
  • Button (59-59)
app/widgets/login-view/model/useLoginView.ts (1)
app/stores/auth-store.ts (2)
  • useIsAuthenticated (102-102)
  • useAuthStore (18-98)
app/routes/login.tsx (4)
app/routes/add-videos.tsx (1)
  • meta (10-13)
app/routes/player.$id.tsx (1)
  • meta (63-75)
app/routes/setup.tsx (1)
  • meta (11-16)
app/pages/login/ui/LoginPage.tsx (1)
  • LoginPage (4-10)
app/pages/login/ui/LoginPage.tsx (2)
app/widgets/login-view/model/useLoginView.ts (1)
  • useLoginView (18-93)
app/widgets/login-view/ui/LoginView.tsx (1)
  • LoginView (21-112)

Addresses CodeRabbit review feedback on PR #54.

Security fixes:
- Validate redirectTo query parameter to prevent open redirect attacks
- Only allow same-origin paths starting with single '/'
- Reject protocol-relative '//' and external URLs
- Applied to both auto-redirect (useEffect) and post-login redirect

Accessibility improvements:
- Add aria-label to password visibility toggle button
- Add title attribute for tooltip support
- Screen readers can now announce button purpose

Verified:
- All quality checks pass (lint, typecheck, test, build)
- E2E tests confirm login flow works correctly
- No breaking changes to existing functionality
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/widgets/login-view/model/useLoginView.ts (1)

35-38: DRY up redirect sanitization.

The same redirect validation block appears twice. Centralizing it keeps the security hardening in one place and avoids accidental drift later.

+const resolveRedirectTo = (params: URLSearchParams) => {
+  const raw = params.get('redirectTo');
+  return raw && raw.startsWith('/') && !raw.startsWith('//') ? raw : '/';
+};
+
 export function useLoginView(): UseLoginViewResult {
@@
-    const rawRedirectTo = searchParams.get('redirectTo');
-    const redirectTo = rawRedirectTo && rawRedirectTo.startsWith('/') && !rawRedirectTo.startsWith('//')
-      ? rawRedirectTo
-      : '/';
-    navigate(redirectTo, { replace: true });
+    navigate(resolveRedirectTo(searchParams), { replace: true });
@@
-        const rawRedirectTo = searchParams.get('redirectTo');
-        const redirectTo = rawRedirectTo && rawRedirectTo.startsWith('/') && !rawRedirectTo.startsWith('//')
-          ? rawRedirectTo
-          : '/';
-        navigate(redirectTo, { replace: true });
+        navigate(resolveRedirectTo(searchParams), { replace: true });

Also applies to: 70-73

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a65172 and ea19de2.

📒 Files selected for processing (2)
  • app/widgets/login-view/model/useLoginView.ts (1 hunks)
  • app/widgets/login-view/ui/LoginView.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/widgets/login-view/ui/LoginView.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript에서 any를 사용하지 않고 모든 함수에 포괄적인 타입 힌트를 제공한다 (strict TS)
로그에 비밀값을 남기지 않도록 모든 콘솔 출력은 민감정보를 마스킹/제거한다

**/*.{ts,tsx}: Use the ~/* path alias (from tsconfig.json) for internal imports
Prefer explicit interfaces when defining object shapes in TypeScript (strict mode)
Avoid using any in TypeScript (project runs in strict mode)
Use two-space indentation throughout TypeScript/TSX files
Name components, hooks, and providers with PascalCase
Name helpers and state setters with camelCase
File names should mirror their primary export

Files:

  • app/widgets/login-view/model/useLoginView.ts
**/*.{ts,tsx,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

코드 주석과 문서는 영어로 작성한다

Files:

  • app/widgets/login-view/model/useLoginView.ts
app/**

📄 CodeRabbit inference engine (AGENTS.md)

Place all React Router app source under app/ following the repository’s feature-sliced layout

Files:

  • app/widgets/login-view/model/useLoginView.ts
app/widgets/**

📄 CodeRabbit inference engine (AGENTS.md)

Put UI compositions in app/widgets/

Files:

  • app/widgets/login-view/model/useLoginView.ts
🧬 Code graph analysis (1)
app/widgets/login-view/model/useLoginView.ts (1)
app/stores/auth-store.ts (2)
  • useIsAuthenticated (102-102)
  • useAuthStore (18-98)

@nbsp1221 nbsp1221 merged commit dc6d969 into main Sep 30, 2025
5 checks passed
@nbsp1221 nbsp1221 deleted the feat/refactor-login-fsd branch September 30, 2025 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments