-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement password change submit flow and validation #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds 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
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/components/models/user/ChangePasswordForm.tsx (3)
🪛 GitHub Actions: Lint and Buildsrc/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)
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. Comment |
There was a problem hiding this 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" directiveThis 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 lookupsauthActionClient 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 UXSet 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
📒 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.
There was a problem hiding this 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 authActionClientYou 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
📒 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 usageGood switch to wrappedAction/isActionLoading and awaiting in onSubmit.
67-69: Alert props look correctProps match BasicAlert signature; good.
|
Nice, the validation improvements in the form are gonna help a lot with user experience. Merging this now. |
This PR implements the actual password change submission logic within the
ChangePasswordFormcomponent.Changes
console.logplaceholder with a call to thechangePasswordaction fromsrc/actions/user.ts.src/components/models/user/ChangePasswordForm.tsx.Summary by CodeRabbit
New Features
Bug Fixes / UX Improvements