Signup form component with email, password, and display name fields, including comprehensive validation.

Location: src/components/forms/SignupForm.tsx

Overview

The SignupForm component provides a complete user registration experience with email, password, and display name fields. It includes robust client-side validation (including password strength requirements), error handling, loading states, and automatic redirection to the dashboard on success. The form creates the user with the Firebase client SDK, then exchanges the ID token via /api/auth/login. It does not POST credentials to a server signup route.

Quick Customisation

This component is highly customizable:

  • Success callback - Custom onSuccess handler for post-signup actions
  • Error callback - Custom onError handler for error handling
  • Redirect destination - Defaults to /dashboard (configure in config.ts)
  • Validation rules - Password strength, email format, required fields
  • Styling - Uses DaisyUI components (Input, Button, Alert)
  • Helper text - Password field includes helper text for requirements

Usage

import SignupForm from "@/components/forms/SignupForm";

<SignupForm />

Props

PropTypeDefaultDescription
onSuccess() => voidundefinedCallback when signup succeeds (overrides default redirect)
onError(error: string) => voidundefinedCallback when signup fails

Examples

Basic Usage

import SignupForm from "@/components/forms/SignupForm";

// Standard signup form
<SignupForm />

With Custom Success Handler

import SignupForm from "@/components/forms/SignupForm";
import { useRouter } from "next/navigation";

function CustomSignup() {
  const router = useRouter();

  return (
    <SignupForm
      onSuccess={() => {
        // Custom logic after signup
        console.log("User registered!");
        router.push("/onboarding");
      }}
    />
  );
}

With Custom Error Handling

import SignupForm from "@/components/forms/SignupForm";

<SignupForm
  onError={(error) => {
    // Custom error handling
    console.error("Signup failed:", error);
    // Show custom notification, etc.
  }}
/>

Complete Example with Both Callbacks

import SignupForm from "@/components/forms/SignupForm";

<SignupForm
  onSuccess={() => {
    // Redirect to onboarding
    router.push("/onboarding");
  }}
  onError={(error) => {
    // Handle error (e.g., show toast notification)
    toast.error(error);
  }}
/>

Features

Form Fields

  • Display Name input - User's display name, required field
  • Email input - Validated email format, required field
  • Password input - Required field with strength validation, masked input
  • Helper text - Password field shows requirements

Validation

  • Display Name - Required field
  • Email format - Validates email pattern (/\S+@\S+\.\S+/)
  • Password strength:
    • Minimum 8 characters
    • At least one letter
    • At least one number
  • Required fields - All fields are required
  • Error display - Shows field-specific and general errors

User Experience

  • Loading state - Shows loading spinner on submit button
  • Disabled inputs - Inputs disabled during submission
  • Auto-redirect - Redirects to /dashboard on success (or custom onSuccess)
  • Error messages - Clear error messages for validation and API errors
  • Helper text - Password field shows requirements below input

Component Structure

interface SignupFormProps {
  onSuccess?: () => void;
  onError?: (error: string) => void;
}

const SignupForm = ({ onSuccess, onError }: SignupFormProps) => {
  // State management
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [displayName, setDisplayName] = useState("");
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState({});

  // Validation and submit logic
  // ...
};

Validation Rules

Display Name

  • Required: Yes
  • Error message: "Display name is required"

Email

  • Required: Yes
  • Format: Must be valid email format (user@example.com)
  • Error message: "Email is required" or "Email is invalid"

Password

  • Required: Yes
  • Minimum length: 8 characters
  • Must contain: At least one letter (a-z, A-Z)
  • Must contain: At least one number (0-9)
  • Error messages:
    • "Password is required"
    • "Password must be at least 8 characters"
    • "Password must contain at least one letter"
    • "Password must contain at least one number"

API Integration

Flow (same session path as login — do not POST credentials to /api/auth/signup from this form):

const credential = await createUserWithEmailAndPassword(auth, email, password);
await updateProfile(credential.user, { displayName });
const idToken = await credential.user.getIdToken(true); // refresh so displayName is in claims

// POST /api/auth/login
{ idToken: string }

Response:

  • Success: Bootstraps users/{uid} if needed, sets httpOnly Firebase session cookie, returns { success: true, data: ... }
  • Error: Firebase client errors (e.g. email already in use) or API error message

Customisation

Change Redirect Destination

Option 1: Use onSuccess prop:

<SignupForm
  onSuccess={() => router.push("/onboarding")}
/>

Option 2: Update config.ts:

// config.ts
auth: {
  callbackUrl: "/your-custom-page",
  // ...
}

Customise Password Requirements

Edit the validate function in SignupForm.tsx:

const validate = () => {
  const newErrors = {};

  // Custom password requirements
  if (password.length < 12) { // Change minimum length
    newErrors.password = "Password must be at least 12 characters";
  }

  if (!/[A-Z]/.test(password)) { // Require uppercase
    newErrors.password = "Password must contain an uppercase letter";
  }

  if (!/[!@#$%^&*]/.test(password)) { // Require special character
    newErrors.password = "Password must contain a special character";
  }

  // ... existing validation

  return Object.keys(newErrors).length === 0;
};

Update helper text:

<Input
  type="password"
  label="Password"
  helperText="Must be at least 12 characters with uppercase, lowercase, number, and special character"
  // ...
/>

Customise Validation

Edit the validate function in SignupForm.tsx:

const validate = () => {
  const newErrors = {};

  // Custom display name validation
  if (displayName.length < 3) {
    newErrors.displayName = "Display name must be at least 3 characters";
  }

  // Custom email validation
  if (email && !email.endsWith("@yourcompany.com")) {
    newErrors.email = "Must use company email";
  }

  // ... existing validation

  return Object.keys(newErrors).length === 0;
};

Customise Styling

The form uses DaisyUI components. Modify classes:

// In SignupForm.tsx
<form onSubmit={handleSubmit} className="space-y-6"> {/* Change spacing */}
  {/* ... */}
</form>

Styling Notes

  • Form container: Uses space-y-4 for vertical spacing
  • Inputs: Uses Input component with validation states
  • Button: Uses Button component with loading state
  • Errors: Uses DaisyUI alert alert-error for error display
  • Helper text: Password field shows requirements below input

Best Practices

  1. Password strength - Current requirements are a good balance (not too strict, not too weak)
  2. Error handling - Always provide onError callback for better UX
  3. Loading states - Form automatically handles loading (inputs disabled)
  4. Accessibility - Form includes proper labels and autoComplete attributes
  5. Security - Password field uses type="password" for masking
  6. User feedback - Helper text helps users understand requirements upfront

Tips

  • Success callback: Overrides default redirect if provided
  • Error callback: Use for custom error notifications (toasts, alerts)
  • Password requirements: Helper text shows requirements to reduce errors
  • Auto-complete: Form includes proper autoComplete attributes
  • Validation: Client-side validation happens before API call
  • Display name: Use for user's display name (can be different from email)

Learn More