Skip to content

Login modal refactor#4268

Open
hlbmtc wants to merge 3 commits intomainfrom
feat/refactored-login-screen
Open

Login modal refactor#4268
hlbmtc wants to merge 3 commits intomainfrom
feat/refactored-login-screen

Conversation

@hlbmtc
Copy link
Contributor

@hlbmtc hlbmtc commented Feb 6, 2026

  • Replaced useActionState with useServerAction
  • Don't reset username field on error
  • Improved login speed (3s -> 1s)

Summary by CodeRabbit

  • New Features

    • Reworked sign-in flow for direct server-based submission, improving post-login handling and analytics.
    • Introduced clearer submission error display and field-level feedback in the sign-in form.
  • Bug Fixes

    • Improved error handling to surface API errors consistently and reset sensitive fields on failure.
    • Enhanced detection and handling of account inactivity during sign-in.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

The login flow was refactored: the server action now accepts explicit login and password parameters instead of FormData, and the SignIn component was updated to call that action directly via a server-action hook with revised error handling and modal navigation.

Changes

Cohort / File(s) Summary
Auth action
front_end/src/app/(main)/accounts/actions.ts
Changed loginAction signature from (prevState, formData) to (login: string, password: string). Removed client-side FormData validation and now calls ServerAuthApi.signIn(login, password). Error returns changed from fieldErrors to ApiErrorPayload.
SignIn UI & hook wiring
front_end/src/components/auth/signin.tsx
Replaced transition/formAction flow with useServerAction and an explicit onSubmit that calls loginAction. Added submitErrors state, FormErrorMessage rendering, password reset on error, and account-inactive modal navigation. Adjusted input error wiring to use returned error shape.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as SignIn Component
    participant Action as loginAction
    participant API as ServerAuthApi
    participant Modal as Modal System

    User->>UI: submit(login, password)
    activate UI
    UI->>Action: loginAction(login, password)
    activate Action
    Action->>API: signIn(login, password)
    activate API
    API-->>Action: success or ApiErrorPayload
    deactivate API

    alt success
        Action-->>UI: token & user
        UI->>UI: analytics, update user context, close modal, redirect
    else error
        Action-->>UI: ApiErrorPayload
        UI->>UI: set submitErrors, clear password
        alt account inactive
            UI->>Modal: open accountInactive
        else other errors
            UI->>UI: display field/submit errors
        end
    end
    deactivate Action
    deactivate UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped from FormData's tangled vine,
To clear-cut fields of login and sign.
A server call now, neat and spry,
Modals flicker, errors shy.
Hooray — credentials leap, and the rabbit sighs!

🚥 Pre-merge checks | ✅ 2
✅ 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 'Login modal refactor' accurately describes the main change: refactoring the sign-in modal component with updated form handling logic and improved login performance.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refactored-login-screen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
front_end/src/app/(main)/accounts/actions.ts (2)

41-55: ⚠️ Potential issue | 🟠 Major

Non-ApiError exceptions result in silent failure — no error shown and no success.

When the caught error is not an ApiError, the function returns { errors: undefined }. On the caller side (signin.tsx), state?.errors is falsy so the error branch is skipped, and state?.user is also undefined so the success branch is skipped. The user sees the form stop loading with zero feedback.

Return a generic error message for the non-ApiError path:

Proposed fix
   } catch (err: unknown) {
     return {
       errors: ApiError.isApiError(err)
         ? (err.data as ApiErrorPayload)
-        : undefined,
+        : { detail: "Something went wrong. Please try again." },
     };
   }

57-73: ⚠️ Potential issue | 🟠 Major

Add revalidatePath after successful login in loginAction.

loginAction (lines 57-73) sets auth tokens but does not call revalidatePath("/"), while signUpAction (line 131) does. Without cache revalidation after setting tokens, Next.js may continue serving cached server-rendered pages with stale auth state (e.g., showing the "Log In" button instead of the user profile) until the cache naturally expires.

Proposed fix
  const authManager = await getAuthCookieManager();
  authManager.setAuthTokens(response.tokens);
+ revalidatePath("/");

  // Set user's language preference as the active locale
🤖 Fix all issues with AI agents
In `@front_end/src/components/auth/signin.tsx`:
- Around line 54-56: The branch handling login errors (the if (state?.errors)
block inside the submit handler in signin.tsx) contradicts the PR goal to
preserve the password: remove the call to resetField("password") so
setSubmitErrors(state.errors as ErrorResponse) does not clear the password;
alternatively, if clearing password was intended, update the PR description to
reflect that behavior—locate the error branch around setSubmitErrors and remove
the resetField("password") call to keep the password value on failed attempts.
- Around line 54-55: Remove the unnecessary cast to ErrorResponse when assigning
state.errors: instead of forcing setSubmitErrors(state.errors as ErrorResponse)
allow submitErrors to hold the true union type (FieldErrorsFrom<...> |
ApiErrorPayload) or narrow at use-site; update the submitErrors state type to
the union (or change the set call to perform a runtime check on state.errors
like the existing checks at lines 59–61) so you no longer mask structural
differences between form validation errors and API payloads—refer to
state.errors, setSubmitErrors, submitErrors, ErrorResponse, FormErrorMessage,
and extractError when making this change.
🧹 Nitpick comments (4)
front_end/src/app/(main)/accounts/actions.ts (1)

35-39: LoginActionState.errors type includes FieldErrorsFrom<typeof signInSchema> which is now dead.

Since loginAction no longer performs schema validation and never returns FieldErrorsFrom, that branch of the union (and transitively the signInSchema import usage in this type, plus the FieldErrorsFrom helper) is unreachable from loginAction. Consider narrowing the type to just ApiErrorPayload if no other consumers rely on the old shape.

front_end/src/components/auth/signin.tsx (3)

58-69: Fragile inactive-account detection — consider a dedicated field or error code.

The check casts to { user_state?: string } and string-compares "inactive". This is tightly coupled to a specific API response shape. If the backend ever renames the field or changes the value, this silently breaks.

Consider having loginAction return a structured discriminant (e.g., isInactive: boolean) instead of pushing API-response introspection into the UI component.


63-68: setCurrentModal(null) immediately before setCurrentModal({...}) is likely unnecessary.

React 18+ batches state updates within the same synchronous block, so the intermediate null value will never be rendered. If the goal is to force a modal remount, a key prop on the modal component would be more explicit and reliable.


49-82: setSubmitErrors missing from useCallback dependency array.

Line 80 lists [setCurrentModal, setUser, handlePostLoginAction, resetField] but omits setSubmitErrors and loginAction. React's useState setters are identity-stable so this won't cause a bug today, but the react-hooks/exhaustive-deps lint rule will flag it.

Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@front_end/src/components/auth/signin.tsx`:
- Line 38: Replace the incorrect ErrorResponse typing on submitErrors with the
actual union returned by loginAction: change the useState generic for
submitErrors to match LoginActionState['errors'] (e.g., use the conditional type
typeof loginAction extends Promise<LoginActionState> ?
NonNullable<LoginActionState>['errors'] : never or explicitly
FieldErrorsFrom<typeof signInSchema> | ApiErrorPayload) so that
submitErrors/setSubmitErrors and checks like state.errors.user_state align with
the runtime shape; update the declaration that currently references submitErrors
and setSubmitErrors accordingly (symbols: submitErrors, setSubmitErrors,
loginAction, LoginActionState, signInSchema, FieldErrorsFrom, ApiErrorPayload).
🧹 Nitpick comments (2)
front_end/src/components/auth/signin.tsx (2)

49-81: Well-structured onSubmit with clear error/success branching.

The refactor from useActionState/useTransition to a useCallback + useServerAction pattern is clean and easier to follow. Error handling, inactive-account detection, and success-path logic are all clearly separated.

Two minor observations:

  1. Lines 64–65 — double setCurrentModal call. setCurrentModal(null) immediately followed by setCurrentModal({type: "accountInactive", …}) will be batched by React 18+, so the intermediate null value is never rendered. If the intent is to force-close the current modal before opening the new one (e.g., triggering an onClose side-effect), verify that this batching doesn't skip that side-effect. If it's just "replace the current modal," the null call is redundant.

  2. Line 80 — setSubmitErrors omitted from the dependency array. Safe at runtime (React guarantees useState setters are stable), but react-hooks/exhaustive-deps will likely warn. Consider adding it for lint compliance, or suppressing with a comment if already configured.


58-69: Inactive-account narrowing uses a type assertion that can be avoided.

After the typeof and in checks on lines 59–60, TypeScript still doesn't narrow state.errors enough, so line 61 uses as { user_state?: string }. This is safe given the preceding guards, but you can avoid the assertion with a small helper or by indexing directly:

Suggested simplification
-        const isInactive =
-          typeof state.errors === "object" &&
-          "user_state" in state.errors &&
-          (state.errors as { user_state?: string }).user_state === "inactive";
+        const isInactive =
+          typeof state.errors === "object" &&
+          state.errors !== null &&
+          "user_state" in state.errors &&
+          (state.errors as Record<string, unknown>).user_state === "inactive";

Or extract a type-guard:

function hasUserState(obj: unknown): obj is { user_state: string } {
  return typeof obj === "object" && obj !== null && "user_state" in obj && typeof (obj as Record<string, unknown>).user_state === "string";
}

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4268-feat-refactored-login-screen-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:feat-refactored-login-screen-e4c9bdd
🗄️ PostgreSQL NeonDB branch preview/pr-4268-feat-refactored-login-screen
Redis Fly Redis mtc-redis-pr-4268-feat-refactored-login-screen

Details

  • Commit: cb6271bcc9115536c429a75d056f2144117b470d
  • Branch: feat/refactored-login-screen
  • Fly App: metaculus-pr-4268-feat-refactored-login-screen

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

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