Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Jan 17, 2026

Summary by CodeRabbit

  • New Features
    • Introduced email and password-based authentication enabling users to create accounts and log in using email credentials.
    • Implemented complete login and signup pages featuring form validation, error messages, and user-friendly layouts.
    • Enhanced authentication UI with visual elements including form separators and improved card-based layouts for better user experience.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Adds email/password authentication support to the application by implementing complete login and signup forms using React Hook Form with Zod validation, alongside enabling the emailAndPassword provider in the auth configuration.

Changes

Cohort / File(s) Summary
Web Authentication Forms
apps/web/app/routes/auth/login.tsx, apps/web/app/routes/auth/signup.tsx
Implements email/password login and signup flows. Login adds LoginSeparator component, signInEmailPasswordSchema, and LoginEmailPassword form component with rememberMe toggle. Signup introduces SignupForm component with name/email/password validation and calls authClient.signUp.email on submit with redirect to /workspaces on success. Both integrate React Hook Form with zodResolver and styled form components.
Authentication Configuration
packages/auth/src/better/config.ts
Enables emailAndPassword authentication provider by adding emailAndPassword: { enabled: true } to the auth configuration object.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Form as Login Form
    participant AuthClient as Auth Client
    participant Backend as Auth Service
    
    User->>Form: Enter email/password & submit
    Note over Form: Zod validation
    Form->>AuthClient: authClient.signIn.email({email, password, rememberMe})
    AuthClient->>Backend: Send credentials
    Backend->>Backend: Verify email/password
    alt Authentication Success
        Backend->>AuthClient: Return session token
        AuthClient->>Form: Success response
        Form->>User: Redirect to callbackURL
    else Authentication Failure
        Backend->>AuthClient: Error response
        AuthClient->>Form: Display error message
        Form->>User: Show validation error
    end
Loading
sequenceDiagram
    actor User
    participant Form as Signup Form
    participant AuthClient as Auth Client
    participant Backend as Auth Service
    
    User->>Form: Enter name/email/password & submit
    Note over Form: Zod validation
    Form->>AuthClient: authClient.signUp.email({name, email, password})
    AuthClient->>Backend: Send registration data
    Backend->>Backend: Validate & create account
    alt Registration Success
        Backend->>AuthClient: Return session token
        AuthClient->>Form: Success response
        Form->>User: Redirect to /workspaces
    else Registration Failure
        Backend->>AuthClient: Error response
        AuthClient->>Form: Display error message
        Form->>User: Show validation error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hop, skip, and form validation bliss—
Email and passwords sealed with a kiss!
LoginSeparator splits the way,
SignupForm dances, auth's here to stay! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'chore: enable credential auth' accurately reflects the main changes: enabling email/password authentication across the codebase by adding login/signup forms and enabling emailAndPassword in the auth config.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@jsbroks jsbroks merged commit f230121 into main Jan 17, 2026
7 of 8 checks passed
@jsbroks jsbroks deleted the enable-creds-auth branch January 17, 2026 01:07
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: 3

🤖 Fix all issues with AI agents
In `@apps/web/app/routes/auth/login.tsx`:
- Around line 66-87: The password input is rendering as plain text; update the
FormField with name="password" so the Input component used in its render has
type="password" to mask characters, and add appropriate autocomplete attributes:
set autocomplete="email" on the FormField/Input for name="email" (or the Input
in that render) and autocomplete="current-password" on the Input for
name="password" to provide browser autofill hints; ensure you modify the Input
props referenced in the render callbacks for those FormField components.
- Around line 55-61: The onSubmit handler currently fire-and-forgets the
authClient.signIn.email call causing silent failures; convert onSubmit into an
async function, await the promise returned by authClient.signIn.email (the call
that spreads data from signInEmailPasswordSchema and sets
rememberMe/callbackURL), and add error handling (try/catch) to handle rejected
promises and surface user feedback or logging on failure; ensure you handle
success vs failure paths consistently with signup.tsx (e.g., show an error
message or set form error on catch, and redirect or proceed on success).

In `@apps/web/app/routes/auth/signup.tsx`:
- Around line 1-3: Add the "use client" directive as the very first line of the
module (before any imports) so the component using the useForm hook from
react-hook-form runs as a Client Component; this ensures calls to useForm (and
other client hooks) will be allowed—update the top of the signup module that
imports zodResolver, useForm, and z accordingly.
🧹 Nitpick comments (3)
apps/web/app/routes/auth/login.tsx (1)

46-53: Consider using the form schema shortcut to drop zodResolver.
If the repo’s shadcn form wrapper still supports useForm({ schema }), this reduces boilerplate and imports. Based on learnings, this pattern is already supported in this codebase.

♻️ Proposed simplification
-import { zodResolver } from "@hookform/resolvers/zod";
 import { useForm } from "react-hook-form";
 import { z } from "zod";
@@
   const form = useForm<z.infer<typeof signInEmailPasswordSchema>>({
-    resolver: zodResolver(signInEmailPasswordSchema),
+    schema: signInEmailPasswordSchema,
     defaultValues: {
       email: "",
       password: "",
     },
   });
apps/web/app/routes/auth/signup.tsx (2)

30-38: Optional: use schema instead of zodResolver.
If the shadcn form wrapper still supports useForm({ schema }), this simplifies configuration and removes a dependency import. Based on learnings, this is supported in this codebase.

♻️ Proposed simplification
-import { zodResolver } from "@hookform/resolvers/zod";
 import { useForm } from "react-hook-form";
 import { z } from "zod";
@@
   const form = useForm<z.infer<typeof signUpSchema>>({
-    resolver: zodResolver(signUpSchema),
+    schema: signUpSchema,
     defaultValues: {
       name: "",
       email: "",
       password: "",
     },
   });

40-90: Surface sign‑up errors to users.
Line 41 only blocks the redirect; the UI never shows why signup failed.

💡 Suggested improvement
   const onSubmit = async (data: z.infer<typeof signUpSchema>) => {
     const { error } = await authClient.signUp.email({ ...data });
-    if (!error) window.location.href = "/workspaces";
+    if (error) {
+      form.setError("root", {
+        message: error.message ?? "Sign-up failed",
+      });
+      return;
+    }
+    window.location.href = "/workspaces";
   };
@@
-        <div className="space-y-2">
+        <div className="space-y-2">
+          {form.formState.errors.root?.message && (
+            <p className="text-sm text-destructive">
+              {form.formState.errors.root.message}
+            </p>
+          )}
           <Button type="submit" className="w-full">
             Sign up
           </Button>

Comment on lines +55 to +61
const onSubmit = (data: z.infer<typeof signInEmailPasswordSchema>) => {
void authClient.signIn.email({
...data,
rememberMe: true,
callbackURL: "/workspaces",
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and the specific file in question
fd auth -t f | grep -E "login|auth" | head -20

Repository: ctrlplanedev/ctrlplane

Length of output: 205


🏁 Script executed:

# Check if the file exists and examine its contents
cat -n apps/web/app/routes/auth/login.tsx | head -100

Repository: ctrlplanedev/ctrlplane

Length of output: 3334


🏁 Script executed:

# Search for authClient definition and usage pattern
rg "authClient" apps/web -A 3 -B 3 --max-count 20

Repository: ctrlplanedev/ctrlplane

Length of output: 3497


🏁 Script executed:

# Examine the auth-client implementation
cat -n apps/web/app/api/auth-client.ts

Repository: ctrlplanedev/ctrlplane

Length of output: 291


🏁 Script executed:

# Check if better-auth or similar is used - look for package.json
fd package.json -t f | head -5 | xargs grep -l "better-auth\|auth" 2>/dev/null

Repository: ctrlplanedev/ctrlplane

Length of output: 109


🏁 Script executed:

# Look for where signIn is actually implemented or what library provides it
rg "signIn\." apps/web/app/api/ -A 5

Repository: ctrlplanedev/ctrlplane

Length of output: 48


🏁 Script executed:

# Check the signup implementation to see how it properly handles authClient
cat -n apps/web/app/routes/auth/signup.tsx | grep -A 10 "onSubmit"

Repository: ctrlplanedev/ctrlplane

Length of output: 788


🏁 Script executed:

# Look for other signIn patterns in the codebase
rg "signIn\." apps/web -B 2 -A 5

Repository: ctrlplanedev/ctrlplane

Length of output: 1000


🏁 Script executed:

# Check package.json to understand better-auth version
cat apps/web/package.json | grep -A 2 -B 2 "better-auth"

Repository: ctrlplanedev/ctrlplane

Length of output: 244


🏁 Script executed:

# Check if there are any other onSubmit handlers or error handling patterns in auth routes
rg "onSubmit.*authClient" apps/web/app/routes/auth -A 8

Repository: ctrlplanedev/ctrlplane

Length of output: 48


🏁 Script executed:

# Check if there's any middleware or global error handler catching these promises
rg "unhandledRejection\|catch.*signIn\|onerror" apps/web -i

Repository: ctrlplanedev/ctrlplane

Length of output: 48


Handle sign‑in errors instead of fire‑and‑forget.

Line 55–61 discards the promise without error handling, creating silent failures and unhandled rejections. The same library (better-auth) is properly awaited in signup.tsx, and coding guidelines prefer async/await with explicit error handling.

🐛 Suggested fix with async/await + user feedback
-  const onSubmit = (data: z.infer<typeof signInEmailPasswordSchema>) => {
-    void authClient.signIn.email({
-      ...data,
-      rememberMe: true,
-      callbackURL: "/workspaces",
-    });
-  };
+  const onSubmit = async (data: z.infer<typeof signInEmailPasswordSchema>) => {
+    const { error } = await authClient.signIn.email({
+      ...data,
+      rememberMe: true,
+      callbackURL: "/workspaces",
+    });
+    if (error) {
+      form.setError("root", {
+        message: error.message ?? "Sign-in failed",
+      });
+      return;
+    }
+  };
@@
-        <div className="space-y-2">
+        <div className="space-y-2">
+          {form.formState.errors.root?.message && (
+            <p className="text-sm text-destructive">
+              {form.formState.errors.root.message}
+            </p>
+          )}
           <Button type="submit" className="w-full">
             Sign in
           </Button>
🤖 Prompt for AI Agents
In `@apps/web/app/routes/auth/login.tsx` around lines 55 - 61, The onSubmit
handler currently fire-and-forgets the authClient.signIn.email call causing
silent failures; convert onSubmit into an async function, await the promise
returned by authClient.signIn.email (the call that spreads data from
signInEmailPasswordSchema and sets rememberMe/callbackURL), and add error
handling (try/catch) to handle rejected promises and surface user feedback or
logging on failure; ensure you handle success vs failure paths consistently with
signup.tsx (e.g., show an error message or set form error on catch, and redirect
or proceed on success).

Comment on lines +66 to +87
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Password field renders as plain text.
Line 86 uses the default input type; this exposes passwords in clear text. Please set type="password" and add email/password autocomplete hints.

🔒 Proposed fix
-                <Input {...field} />
+                <Input type="email" autoComplete="email" {...field} />
@@
-                <Input {...field} />
+                <Input
+                  type="password"
+                  autoComplete="current-password"
+                  {...field}
+                />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" autoComplete="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
autoComplete="current-password"
{...field}
/>
</FormControl>
🤖 Prompt for AI Agents
In `@apps/web/app/routes/auth/login.tsx` around lines 66 - 87, The password input
is rendering as plain text; update the FormField with name="password" so the
Input component used in its render has type="password" to mask characters, and
add appropriate autocomplete attributes: set autocomplete="email" on the
FormField/Input for name="email" (or the Input in that render) and
autocomplete="current-password" on the Input for name="password" to provide
browser autofill hints; ensure you modify the Input props referenced in the
render callbacks for those FormField components.

Comment on lines +1 to +3
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the signup and login files
fd -t f "signup\.(ts|tsx)" apps/web
fd -t f "login\.(ts|tsx)" apps/web

Repository: ctrlplanedev/ctrlplane

Length of output: 136


🏁 Script executed:

# Check the signup file content
cat -n apps/web/app/routes/auth/signup.tsx | head -50

# Check the login file content for comparison
cat -n apps/web/app/routes/auth/login.tsx | head -50

Repository: ctrlplanedev/ctrlplane

Length of output: 3244


🏁 Script executed:

# Get full signup.tsx to see if there are other client-side features
wc -l apps/web/app/routes/auth/signup.tsx

# Check what the Form component is and if it requires client
rg "export.*Form" apps/web/app/components/ui/form -A 3

Repository: ctrlplanedev/ctrlplane

Length of output: 194


🏁 Script executed:

# Find the Form component
fd -t f "form\.(ts|tsx)" apps/web/app/components

# Also check the structure
ls -la apps/web/app/components/ui/ | head -20

Repository: ctrlplanedev/ctrlplane

Length of output: 1291


🏁 Script executed:

# Check the form.tsx file
cat -n apps/web/app/components/ui/form.tsx | head -30

Repository: ctrlplanedev/ctrlplane

Length of output: 1115


🏁 Script executed:

# Verify the structure - is signup.tsx in app router context?
cat -n apps/web/app/routes/auth/signup.tsx | tail -50

# Also check if signup is exported as a default component
grep -n "export" apps/web/app/routes/auth/signup.tsx | head -10

Repository: ctrlplanedev/ctrlplane

Length of output: 2135


🏁 Script executed:

# Check if there's a pattern in the codebase - do other route components have "use client"?
fd -t f "\.tsx$" apps/web/app/routes --exec head -1 {} + | grep -B 1 "use client" | head -20

# Check the app structure - is there a layout.tsx?
cat -n apps/web/app/layout.tsx | head -20

Repository: ctrlplanedev/ctrlplane

Length of output: 6966


🏁 Script executed:

# Check app structure
find apps/web/app -name "layout.tsx" -type f

# Also check for global configuration that might make routes client by default
rg "use client" apps/web/app --files-with-matches | head -10

Repository: ctrlplanedev/ctrlplane

Length of output: 456


🏁 Script executed:

# Check if there's a custom useForm or form initialization pattern in ctrlplane
rg "useForm.*schema:" apps/web --context 3 | head -30

# Also check the signup form more carefully
grep -n "const form = " apps/web/app/routes/auth/signup.tsx -A 5

Repository: ctrlplanedev/ctrlplane

Length of output: 253


Add "use client" to enable hook usage.

This file uses the useForm hook from react-hook-form; without the client directive it will fail in a Server Component context. The login.tsx file correctly includes this directive.

Proposed fix
+"use client";
+
 import { zodResolver } from "@hookform/resolvers/zod";
 import { useForm } from "react-hook-form";
 import { z } from "zod";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
🤖 Prompt for AI Agents
In `@apps/web/app/routes/auth/signup.tsx` around lines 1 - 3, Add the "use client"
directive as the very first line of the module (before any imports) so the
component using the useForm hook from react-hook-form runs as a Client
Component; this ensures calls to useForm (and other client hooks) will be
allowed—update the top of the signup module that imports zodResolver, useForm,
and z accordingly.

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.

3 participants