Skip to content

Conversation

deniswaker
Copy link
Collaborator

@deniswaker deniswaker commented Oct 15, 2025

This PR implements the actual password change submission logic within the ChangePasswordForm component.

Changes

  • Replaced the console.log placeholder with a call to the changePassword action from src/actions/user.ts.
  • Integrated form-level validation feedback to guide users on input errors.
  • Added a loading state to the form, providing visual cues during submission.
  • All changes are contained within src/components/models/user/ChangePasswordForm.tsx.

Summary by CodeRabbit

  • New Features

    • Change password flow: secure server-side password update with validation and immediate profile refresh.
    • Profile updates: edit display name with immediate profile refresh.
  • Bug Fixes / UX Improvements

    • Password form now shows success toasts, field-level errors, a general error banner, and disables submit while processing.
    • Improved error handling with clear user feedback on failures.

@coderabbitai
Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

Adds concrete server actions for updating user profile and changing passwords (Zod validation, Prisma updates, bcrypt verification/hashing, auth checks, Next revalidation) and connects the ChangePasswordForm to the server action via a client-side useAction wrapper with mapped fieldErrors, general alert, toasts, and loading state.

Changes

Cohort / File(s) Summary of Changes
User server actions
src/actions/user.ts
Implemented updateProfile (Zod schema, auth check, Prisma update, revalidatePath) and changePassword (Zod schema, locate user, bcrypt.compare, bcrypt.hash, update passwordHash, structured fieldErrors, revalidatePath, error handling).
Change password form (client UX & action integration)
src/components/models/user/ChangePasswordForm.tsx
Replaced placeholder submit with useAction(changePassword), added formError state and BasicAlert, mapped server fieldErrors to form.setError, handled success toast and form reset, displayed error toasts, and disabled submit while action is loading.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant F as ChangePasswordForm (client)
  participant A as useAction Wrapper
  participant S as changePassword (server action)
  participant DB as Prisma/User
  participant C as Next Cache

  U->>F: Submit {currentPassword, newPassword, confirmNewPassword}
  F->>A: runAction(payload)
  A->>S: invoke changePassword(payload)
  S->>DB: find user by session.userId
  alt user not found
    S-->>A: throw Error("User not found")
    A-->>F: onError(general)
    F-->>U: show toast/alert
  else user found
    S->>S: bcrypt.compare(currentPassword, passwordHash)
    alt invalid current password
      S-->>A: return fieldErrors.currentPassword
      A-->>F: set field error
      F-->>U: mark currentPassword error
    else valid
      S->>S: bcrypt.hash(newPassword)
      S->>DB: update passwordHash
      S->>C: revalidatePath("/settings/profile")
      S-->>A: success
      A-->>F: onSuccess()
      F-->>U: show success toast, reset form
    end
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant F as ProfileForm (client)
  participant S as updateProfile (server action)
  participant DB as Prisma/User
  participant C as Next Cache

  U->>F: Submit {name}
  F->>S: invoke updateProfile({ name })
  S->>DB: update user.name
  S->>C: revalidatePath("/settings/profile")
  S-->>F: success
  F-->>U: show success toast/state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • fehranbit

Poem

I thump my paws on keys so spry,
Hashes hummed and cached pages fly.
Names refreshed and passwords new,
Alerts that hop to help you through.
A rabbit cheers — commit in view. 🐇✨

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 "feat: Implement password change submit flow and validation" is clearly related to the changeset. The PR modifies both src/actions/user.ts (implementing the changePassword action with validation schemas and password verification logic) and src/components/models/user/ChangePasswordForm.tsx (implementing form submission handling with error management and validation display). The title accurately captures the primary objective described in the PR summary: implementing password change submission logic with proper validation feedback. The phrase "submit flow and validation" appropriately encompasses both the server-side action implementation and client-side form handling that comprise the password change feature.
✨ 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/change-password-form-submit

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8cbf4c and e5699bf.

📒 Files selected for processing (1)
  • src/components/models/user/ChangePasswordForm.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/models/user/ChangePasswordForm.tsx (3)
src/hooks/use-action.ts (1)
  • useAction (6-22)
src/actions/user.ts (1)
  • changePassword (42-68)
src/components/common/BasicAlert.tsx (1)
  • BasicAlert (5-21)
🪛 GitHub Actions: Lint and Build
src/components/models/user/ChangePasswordForm.tsx

[error] 10-10: Next.js: You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

🔇 Additional comments (4)
src/components/models/user/ChangePasswordForm.tsx (4)

13-20: LGTM! Type-safe error handling.

The ActionErrorShape type and isActionError type guard properly replace the previous any type usage, correctly handling both standard Error objects and validation errors with field-specific messages.


40-40: LGTM! Correct hook usage and state initialization.

The formError state and useAction hook are properly initialized, matching the hook's signature ({ wrappedAction, isActionLoading }) and addressing previous feedback about API mismatches.

Also applies to: 50-50


52-77: LGTM! Comprehensive error handling with appropriate user feedback.

The error handling correctly distinguishes between field-specific validation errors (displayed inline via form.setError) and general errors (displayed via BasicAlert). The pattern of clearing formError when fieldErrors exist (line 68) ensures the persistent alert only shows for general errors, while per-field messages appear inline—this provides clear, scoped feedback to the user.


81-83: LGTM! Correct component props and loading state.

The BasicAlert now uses the correct props (variant, title, description) matching its signature, and the submit button properly reflects the loading state via isActionLoading.

Also applies to: 111-111


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

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/models/user/ChangePasswordForm.tsx (1)

1-1: Add "use client" directive

This component uses useState/react-hook-form; mark as Client Component to fix the Next.js build error.

Apply:

+'use client';
+
 import { Button } from "@/components/ui/button";
🧹 Nitpick comments (2)
src/actions/user.ts (1)

21-31: Prefer ctx from authActionClient; avoid extra session/DB lookups

authActionClient already authenticates and injects ctx.user. Using getAuthenticatedUserId + extra queries is redundant and adds latency. Use ctx.user.id (and ctx.user) directly.

Example diff:

-export const updateProfile = authActionClient.schema(updateProfileSchema).action(async ({ parsedInput }) => {
-  const userId = await getAuthenticatedUserId();
-
-  await prisma.user.update({
-    where: { id: userId },
-    data: { name: parsedInput.name },
-  });
+export const updateProfile = authActionClient.schema(updateProfileSchema).action(async ({ parsedInput, ctx }) => {
+  await prisma.user.update({
+    where: { id: ctx.user.id },
+    data: { name: parsedInput.name },
+  });
   revalidatePath("/profile");
   return { success: true };
 });
 
-export const changePassword = authActionClient.schema(changePasswordSchema).action(async ({ parsedInput }) => {
-  const userId = await getAuthenticatedUserId();
-
-  const user = await prisma.user.findUnique({
-    where: { id: userId },
-  });
+export const changePassword = authActionClient.schema(changePasswordSchema).action(async ({ parsedInput, ctx }) => {
+  const user = ctx.user;
 
   if (!user || !user.passwordHash) {
     throw new Error("User not found or password not set.");
   }
 
   const passwordMatch = await bcrypt.compare(parsedInput.currentPassword, user.passwordHash);
 
   if (!passwordMatch) {
     throw new Error("Invalid current password.");
   }
 
   const hashedNewPassword = await bcrypt.hash(parsedInput.newPassword, 10);
 
   await prisma.user.update({
-    where: { id: userId },
+    where: { id: user.id },
     data: { passwordHash: hashedNewPassword },
   });
   revalidatePath("/profile");
   return { success: true };
 });

Also applies to: 42-68

src/components/models/user/ChangePasswordForm.tsx (1)

73-74: Improve password field autocomplete for UX

Set appropriate autocomplete attributes.

Apply:

-        <Input id="currentPassword" type="password" {...form.register("currentPassword")} />
+        <Input id="currentPassword" type="password" autoComplete="current-password" {...form.register("currentPassword")} />
...
-        <Input id="newPassword" type="password" {...form.register("newPassword")} />
+        <Input id="newPassword" type="password" autoComplete="new-password" {...form.register("newPassword")} />
...
-        <Input id="confirmNewPassword" type="password" {...form.register("confirmNewPassword")} />
+        <Input id="confirmNewPassword" type="password" autoComplete="new-password" {...form.register("confirmNewPassword")} />

Also applies to: 81-82, 90-91

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf214b7 and a9c5aa6.

📒 Files selected for processing (2)
  • src/actions/user.ts (2 hunks)
  • src/components/models/user/ChangePasswordForm.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/models/user/ChangePasswordForm.tsx (3)
src/hooks/use-action.ts (1)
  • useAction (6-22)
src/actions/user.ts (1)
  • changePassword (42-68)
src/components/common/BasicAlert.tsx (1)
  • BasicAlert (5-21)
src/actions/user.ts (2)
src/lib/action.ts (2)
  • authActionClient (33-65)
  • getAuthenticatedUserId (100-106)
src/lib/prisma.ts (1)
  • prisma (7-7)
🪛 GitHub Actions: Lint and Build
src/components/models/user/ChangePasswordForm.tsx

[error] 10-10: Next.js: 'useState' is being used in a Server Component. This component (ChangePasswordForm.tsx) or its parents are not marked as 'use client'. Mark the component with 'use client' or refactor to a Client Component.

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

🧹 Nitpick comments (2)
src/actions/user.ts (2)

21-27: Avoid double-auth lookup; use ctx.user.id from authActionClient

You already gate with authActionClient. Use ctx.user.id instead of calling getAuthenticatedUserId() again.

-export const updateProfile = authActionClient.schema(updateProfileSchema).action(async ({ parsedInput }) => {
-  const userId = await getAuthenticatedUserId();
+export const updateProfile = authActionClient
+  .schema(updateProfileSchema)
+  .action(async ({ ctx, parsedInput }) => {
+  const userId = ctx.user.id;

42-68: Use ctx.user.id and return field-level errors for invalid current password

  • Same as above: prefer ctx.user.id over getAuthenticatedUserId().
  • To support the form’s field mapping, return/throw a structured error with fieldErrors.currentPassword instead of a generic Error.
-export const changePassword = authActionClient.schema(changePasswordSchema).action(async ({ parsedInput }) => {
-  const userId = await getAuthenticatedUserId();
+export const changePassword = authActionClient
+  .schema(changePasswordSchema)
+  .action(async ({ ctx, parsedInput }) => {
+  const userId = ctx.user.id;
@@
-  if (!passwordMatch) {
-    throw new Error("Invalid current password.");
-  }
+  if (!passwordMatch) {
+    // If your action framework supports structured errors, prefer field-level feedback:
+    // e.g., throw new ActionError("Invalid current password.", { fieldErrors: { currentPassword: ["Invalid current password."] } });
+    // Otherwise, return a conventional shape your client recognizes:
+    return Promise.reject({
+      message: "Invalid current password.",
+      fieldErrors: { currentPassword: ["Invalid current password."] }
+    });
+  }

Please confirm your action framework’s error contract (e.g., next-safe-action’s ActionError) so we can tailor this to the supported pattern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9c5aa6 and d8cbf4c.

📒 Files selected for processing (2)
  • src/actions/user.ts (2 hunks)
  • src/components/models/user/ChangePasswordForm.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/actions/user.ts (2)
src/lib/action.ts (2)
  • authActionClient (33-65)
  • getAuthenticatedUserId (100-106)
src/lib/prisma.ts (1)
  • prisma (7-7)
src/components/models/user/ChangePasswordForm.tsx (3)
src/hooks/use-action.ts (1)
  • useAction (6-22)
src/actions/user.ts (1)
  • changePassword (42-68)
src/components/common/BasicAlert.tsx (1)
  • BasicAlert (5-21)
🪛 GitHub Actions: Lint and Build
src/components/models/user/ChangePasswordForm.tsx

[error] 49-49: Unexpected any. Specify a different type @typescript-eslint/no-explicit-any

🪛 GitHub Check: lint-and-build
src/components/models/user/ChangePasswordForm.tsx

[failure] 49-49:
Unexpected any. Specify a different type

🔇 Additional comments (2)
src/components/models/user/ChangePasswordForm.tsx (2)

41-49: Correct useAction usage

Good switch to wrappedAction/isActionLoading and awaiting in onSubmit.


67-69: Alert props look correct

Props match BasicAlert signature; good.

@fehranbit
Copy link
Member

Nice, the validation improvements in the form are gonna help a lot with user experience. Merging this now.

@fehranbit fehranbit merged commit 7c83d68 into main Oct 16, 2025
1 of 2 checks passed
@fehranbit fehranbit deleted the feat/change-password-form-submit branch October 16, 2025 03:04
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.

2 participants