Skip to content

Registration Form Implementation Guide

This document provides detailed documentation of the RegisterPage.tsx component, including the password strength indicator, real-time validation feedback, TypeScript interfaces, and GraphQL integration.

The RegisterPage component is a comprehensive user registration form built with React, TypeScript, shadcn/ui components, and GraphQL. It features advanced password validation, real-time feedback, and accessibility enhancements.

frontend/src/components/auth/RegisterPage.tsx
├── Imports and Dependencies
├── GraphQL Mutation Definition
├── TypeScript Interfaces
├── Password Requirements Configuration
├── Component State Management
├── Validation Functions
├── Event Handlers
├── UI Rendering
└── Export
interface RegisterFormData {
email: string;
password: string;
confirmPassword: string;
acceptTerms: boolean;
}
interface RegisterError {
message: string;
field?: string;
}
interface PasswordRequirement {
label: string;
test: (password: string) => boolean;
}

Key Features:

  • RegisterFormData: Strongly typed form state
  • RegisterError: Error handling with optional field targeting
  • PasswordRequirement: Configurable password validation rules
const REGISTER_MUTATION = gql`
mutation Register($email: String!, $password: String!, $confirmPassword: String!) {
register(email: $email, password: $password, confirmPassword: $confirmPassword) {
token
user {
id
email
}
}
}
`;

Features:

  • Type Safety: Strongly typed variables and response
  • Error Handling: Comprehensive error catching and display
  • Token Management: Automatic token storage on success
  • Navigation: Redirect to dashboard after registration
const [registerMutation] = useMutation(REGISTER_MUTATION);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
if (!validateForm()) {
return;
}
setIsLoading(true);
try {
const { data } = await registerMutation({
variables: {
email: formData.email,
password: formData.password,
confirmPassword: formData.confirmPassword,
},
});
if (data.register.token) {
localStorage.setItem('authToken', data.register.token);
navigate('/', { replace: true });
}
} catch (err: any) {
setError({ message: err.message || 'Registration failed. Please try again.' });
} finally {
setIsLoading(false);
}
};
const passwordRequirements: PasswordRequirement[] = [
{ label: 'At least 8 characters', test: (pwd) => pwd.length >= 8 },
{ label: 'Contains uppercase letter', test: (pwd) => /[A-Z]/.test(pwd) },
{ label: 'Contains lowercase letter', test: (pwd) => /[a-z]/.test(pwd) },
{ label: 'Contains number', test: (pwd) => /\d/.test(pwd) },
{ label: 'Contains special character', test: (pwd) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd) },
];

Features:

  • Configurable Rules: Easy to modify or extend requirements
  • Regex Validation: Robust pattern matching
  • User-Friendly Labels: Clear requirement descriptions
  • Functional Approach: Pure functions for testing
const getPasswordStrength = (): { score: number; label: string; color: string } => {
const metRequirements = passwordRequirements.filter(req => req.test(formData.password)).length;
if (metRequirements <= 2) return { score: metRequirements, label: 'Weak', color: 'text-red-600' };
if (metRequirements <= 3) return { score: metRequirements, label: 'Fair', color: 'text-yellow-600' };
if (metRequirements <= 4) return { score: metRequirements, label: 'Good', color: 'text-blue-600' };
return { score: metRequirements, label: 'Strong', color: 'text-green-600' };
};

Strength Levels:

  • Weak (0-2 requirements): Red indicator
  • Fair (3 requirements): Yellow indicator
  • Good (4 requirements): Blue indicator
  • Strong (5 requirements): Green indicator
{/* Password Strength Indicator */}
{formData.password && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500">Password strength:</span>
<span className={`text-xs font-medium ${passwordStrength.color}`}>
{passwordStrength.label}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1">
<div
className={`h-1 rounded-full transition-all duration-300 ${
passwordStrength.score <= 2 ? 'bg-red-500' :
passwordStrength.score <= 3 ? 'bg-yellow-500' :
passwordStrength.score <= 4 ? 'bg-blue-500' : 'bg-green-500'
}`}
style={{ width: `${(passwordStrength.score / 5) * 100}%` }}
/>
</div>
</div>
)}
{/* Password Requirements */}
{showPasswordRequirements && (
<div className="space-y-1 p-3 bg-gray-50 rounded-md">
<p className="text-xs font-medium text-gray-700 mb-2">Password must contain:</p>
{passwordRequirements.map((req, index) => {
const isMet = req.test(formData.password);
return (
<div key={index} className="flex items-center gap-2 text-xs">
{isMet ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<X className="h-3 w-3 text-gray-400" />
)}
<span className={isMet ? 'text-green-600' : 'text-gray-500'}>
{req.label}
</span>
</div>
);
})}
</div>
)}

Features:

  • Dynamic Display: Shows when user starts typing
  • Visual Indicators: Check/X icons for each requirement
  • Color Coding: Green for met, gray for unmet requirements
  • Real-Time Updates: Updates as user types
{/* Password Match Indicator */}
{formData.confirmPassword && (
<div className="flex items-center gap-2 text-xs">
{formData.password === formData.confirmPassword ? (
<>
<Check className="h-3 w-3 text-green-600" />
<span className="text-green-600">Passwords match</span>
</>
) : (
<>
<X className="h-3 w-3 text-red-600" />
<span className="text-red-600">Passwords do not match</span>
</>
)}
</div>
)}

Features:

  • Instant Feedback: Shows match status immediately
  • Visual Confirmation: Clear success/error indicators
  • User-Friendly: Prevents form submission confusion
const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validatePassword = (password: string): boolean => {
return passwordRequirements.every(req => req.test(password));
};
const validateForm = (): boolean => {
if (!formData.email) {
setError({ message: 'Email is required', field: 'email' });
return false;
}
if (!validateEmail(formData.email)) {
setError({ message: 'Please enter a valid email address', field: 'email' });
return false;
}
if (!formData.password) {
setError({ message: 'Password is required', field: 'password' });
return false;
}
if (!validatePassword(formData.password)) {
setError({ message: 'Password does not meet requirements', field: 'password' });
return false;
}
if (!formData.confirmPassword) {
setError({ message: 'Password confirmation is required', field: 'confirmPassword' });
return false;
}
if (formData.password !== formData.confirmPassword) {
setError({ message: 'Passwords do not match', field: 'confirmPassword' });
return false;
}
if (!formData.acceptTerms) {
setError({ message: 'You must accept the terms and conditions', field: 'acceptTerms' });
return false;
}
return true;
};
const [formData, setFormData] = useState<RegisterFormData>({
email: '',
password: '',
confirmPassword: '',
acceptTerms: false,
});
const [error, setError] = useState<RegisterError | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [showPasswordRequirements, setShowPasswordRequirements] = useState(false);
const handleInputChange = (field: keyof RegisterFormData) => (
e: React.ChangeEvent<HTMLInputElement>
) => {
const value = field === 'acceptTerms' ? e.target.checked : e.target.value;
setFormData(prev => ({ ...prev, [field]: value }));
// Clear field-specific errors when user starts typing
if (error?.field === field) {
setError(null);
}
// Show password requirements when user starts typing password
if (field === 'password' && typeof value === 'string') {
setShowPasswordRequirements(value.length > 0);
}
};
<form onSubmit={handleSubmit} className="space-y-4">
{/* Global Error Message */}
{error && !error.field && (
<div className="flex items-center gap-2 p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md">
<AlertCircle className="h-4 w-4 flex-shrink-0" />
<span>{error.message}</span>
</div>
)}
<Input
className={error?.field === 'email' ? 'border-red-500 focus:border-red-500' : ''}
/>
{error?.field === 'email' && (
<p className="text-sm text-red-600">{error.message}</p>
)}
<div className="flex items-start space-x-2">
<Checkbox
id="accept-terms"
checked={formData.acceptTerms}
onCheckedChange={(checked) =>
setFormData(prev => ({ ...prev, acceptTerms: checked as boolean }))
}
disabled={isLoading}
className={error?.field === 'acceptTerms' ? 'border-red-500' : ''}
/>
<div className="grid gap-1.5 leading-none">
<Label htmlFor="accept-terms" className="text-sm font-normal cursor-pointer">
I agree to the{' '}
<Link to="/terms" className="text-primary hover:text-primary/80 underline">
Terms of Service
</Link>{' '}
and{' '}
<Link to="/privacy" className="text-primary hover:text-primary/80 underline">
Privacy Policy
</Link>
</Label>
</div>
</div>
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<Card className="shadow-lg">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-semibold text-center">Sign up</CardTitle>
<CardDescription className="text-center">
Enter your information to create your account
</CardDescription>
</CardHeader>
<CardContent>
<form className="space-y-4">
  • Minimum Length: 8 characters
  • Character Diversity: Upper, lower, number, special
  • Strength Indication: Visual feedback for users
  • Real-Time Validation: Immediate requirement checking
  • Client-Side Validation: Immediate feedback
  • Server-Side Validation: Backend verification required
  • CSRF Protection: Form tokens (to be implemented)
  • Rate Limiting: Backend implementation needed
// Example test structure
describe('RegisterPage', () => {
test('validates email format', () => {
// Test email validation logic
});
test('checks password requirements', () => {
// Test password strength calculation
});
test('handles form submission', () => {
// Test form submission flow
});
});
// Example integration test
test('complete registration flow', async () => {
render(<RegisterPage />);
// Fill form
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
// Submit and verify
fireEvent.click(screen.getByRole('button', { name: /create account/i }));
// Assert success
await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true });
});
});

Consider implementing debounced validation for better performance:

import { useMemo } from 'react';
import { debounce } from 'lodash';
const debouncedValidation = useMemo(
() => debounce((password: string) => {
// Perform expensive validation
}, 300),
[]
);
const PasswordRequirements = React.memo(({
requirements,
password
}: {
requirements: PasswordRequirement[];
password: string;
}) => {
// Component implementation
});

This comprehensive implementation provides a robust, user-friendly registration experience with strong TypeScript support, real-time feedback, and accessibility considerations.