diff --git a/packages/auth-ui/src/AuthPageLayout.tsx b/packages/auth-ui/src/AuthPageLayout.tsx
new file mode 100644
index 00000000..26a28d2f
--- /dev/null
+++ b/packages/auth-ui/src/AuthPageLayout.tsx
@@ -0,0 +1,101 @@
+import type { AuthPageLayoutProps } from './types.js';
+
+/**
+ * Full-page auth layout — centered card with product branding.
+ * Wraps any auth form (LoginForm, RegisterForm, etc.).
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function AuthPageLayout({
+ productName,
+ logo,
+ title,
+ subtitle,
+ children,
+ footer,
+ className,
+}: AuthPageLayoutProps) {
+ return (
+
+
+ {/* Branding */}
+
+ {logo && (
+
+ {typeof logo === 'string' ? (
+

+ ) : (
+ logo
+ )}
+
+ )}
+
+ {productName}
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+
+ {/* Form content */}
+ {children}
+
+ {/* Footer */}
+ {footer && (
+
+ {footer}
+
+ )}
+
+
+ );
+}
diff --git a/packages/auth-ui/src/ForgotPasswordForm.tsx b/packages/auth-ui/src/ForgotPasswordForm.tsx
new file mode 100644
index 00000000..94c7125b
--- /dev/null
+++ b/packages/auth-ui/src/ForgotPasswordForm.tsx
@@ -0,0 +1,111 @@
+import { useState, type FormEvent } from 'react';
+import type { ForgotPasswordFormProps } from './types.js';
+
+/**
+ * Forgot password form — email input to request a reset link.
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function ForgotPasswordForm({
+ onSubmit,
+ isLoading = false,
+ error,
+ success,
+ onBack,
+ className,
+}: ForgotPasswordFormProps) {
+ const [email, setEmail] = useState('');
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ onSubmit(email);
+ }
+
+ const inputStyle = {
+ padding: '10px 12px',
+ border: '1px solid var(--bl-border, #ccc)',
+ borderRadius: 'var(--bl-radius, 6px)',
+ fontSize: '14px',
+ width: '100%',
+ boxSizing: 'border-box' as const,
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/auth-ui/src/OnboardingShell.tsx b/packages/auth-ui/src/OnboardingShell.tsx
new file mode 100644
index 00000000..a0d9b5a9
--- /dev/null
+++ b/packages/auth-ui/src/OnboardingShell.tsx
@@ -0,0 +1,148 @@
+import type { OnboardingShellProps } from './types.js';
+
+/**
+ * Onboarding shell — step indicator, navigation, progress bar.
+ * Wraps arbitrary step content provided via children.
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function OnboardingShell({
+ steps,
+ currentStep,
+ onNext,
+ onBack,
+ onComplete,
+ children,
+ className,
+}: OnboardingShellProps) {
+ const isFirst = currentStep === 0;
+ const isLast = currentStep === steps.length - 1;
+ const progress = steps.length > 1 ? ((currentStep + 1) / steps.length) * 100 : 100;
+
+ return (
+
+ {/* Progress bar */}
+
+
+ {/* Step indicator */}
+
+ {steps.map((step, i) => (
+
+
+ {i < currentStep ? '✓' : i + 1}
+
+ {step.label}
+
+ ))}
+
+
+ {/* Step content */}
+
+ {children}
+
+
+ {/* Navigation */}
+
+
+
+
+
+
+ );
+}
diff --git a/packages/auth-ui/src/PasswordStrengthBar.tsx b/packages/auth-ui/src/PasswordStrengthBar.tsx
new file mode 100644
index 00000000..9c3e2b6c
--- /dev/null
+++ b/packages/auth-ui/src/PasswordStrengthBar.tsx
@@ -0,0 +1,67 @@
+import { useMemo } from 'react';
+import type { PasswordStrength } from './types.js';
+
+interface PasswordStrengthBarProps {
+ password: string;
+ className?: string;
+}
+
+const STRENGTH_CONFIG: Record = {
+ weak: { color: 'var(--bl-error, #dc3545)', label: 'Weak' },
+ fair: { color: 'var(--bl-warning, #f59e0b)', label: 'Fair' },
+ good: { color: 'var(--bl-info, #3b82f6)', label: 'Good' },
+ strong: { color: 'var(--bl-success, #22c55e)', label: 'Strong' },
+};
+
+export function getPasswordStrength(password: string): PasswordStrength {
+ let score = 0;
+ if (password.length >= 8) score++;
+ if (password.length >= 12) score++;
+ if (/[A-Z]/.test(password)) score++;
+ if (/[a-z]/.test(password)) score++;
+ if (/\d/.test(password)) score++;
+ if (/[^A-Za-z0-9]/.test(password)) score++;
+
+ if (score <= 2) return 'weak';
+ if (score <= 3) return 'fair';
+ if (score <= 4) return 'good';
+ return 'strong';
+}
+
+export function PasswordStrengthBar({ password, className }: PasswordStrengthBarProps) {
+ const strength = useMemo(() => getPasswordStrength(password), [password]);
+ const config = STRENGTH_CONFIG[strength];
+ const widthPercent = { weak: 25, fair: 50, good: 75, strong: 100 }[strength];
+
+ if (!password) return null;
+
+ return (
+
+ );
+}
diff --git a/packages/auth-ui/src/RegisterForm.tsx b/packages/auth-ui/src/RegisterForm.tsx
new file mode 100644
index 00000000..0ab1b1b1
--- /dev/null
+++ b/packages/auth-ui/src/RegisterForm.tsx
@@ -0,0 +1,226 @@
+import { useState, type FormEvent } from 'react';
+import { PasswordStrengthBar } from './PasswordStrengthBar.js';
+import type { RegisterFormProps } from './types.js';
+
+/**
+ * Registration form with name, email, password, confirm password,
+ * password strength indicator, and optional terms checkbox.
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function RegisterForm({
+ onSubmit,
+ isLoading = false,
+ error,
+ termsUrl,
+ privacyUrl,
+ onSwitchToLogin,
+ className,
+}: RegisterFormProps) {
+ const [name, setName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirm, setConfirm] = useState('');
+ const [termsAccepted, setTermsAccepted] = useState(!termsUrl);
+
+ const passwordMismatch = confirm.length > 0 && password !== confirm;
+ const canSubmit =
+ name.trim().length > 0 &&
+ email.length > 0 &&
+ password.length >= 8 &&
+ !passwordMismatch &&
+ termsAccepted &&
+ !isLoading;
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ if (!canSubmit) return;
+ onSubmit({ name: name.trim(), email, password });
+ }
+
+ const inputStyle = {
+ padding: '10px 12px',
+ border: '1px solid var(--bl-border, #ccc)',
+ borderRadius: 'var(--bl-radius, 6px)',
+ fontSize: '14px',
+ width: '100%',
+ boxSizing: 'border-box' as const,
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/auth-ui/src/ResetPasswordForm.tsx b/packages/auth-ui/src/ResetPasswordForm.tsx
new file mode 100644
index 00000000..bd6454e0
--- /dev/null
+++ b/packages/auth-ui/src/ResetPasswordForm.tsx
@@ -0,0 +1,131 @@
+import { useState, type FormEvent } from 'react';
+import { PasswordStrengthBar } from './PasswordStrengthBar.js';
+import type { ResetPasswordFormProps } from './types.js';
+
+/**
+ * Reset password form — new password + confirm, with strength indicator.
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function ResetPasswordForm({
+ onSubmit,
+ isLoading = false,
+ error,
+ success,
+ className,
+}: ResetPasswordFormProps) {
+ const [password, setPassword] = useState('');
+ const [confirm, setConfirm] = useState('');
+
+ const passwordMismatch = confirm.length > 0 && password !== confirm;
+ const canSubmit = password.length >= 8 && !passwordMismatch && !isLoading;
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ if (!canSubmit) return;
+ onSubmit(password);
+ }
+
+ const inputStyle = {
+ padding: '10px 12px',
+ border: '1px solid var(--bl-border, #ccc)',
+ borderRadius: 'var(--bl-radius, 6px)',
+ fontSize: '14px',
+ width: '100%',
+ boxSizing: 'border-box' as const,
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/auth-ui/src/VerifyEmailForm.tsx b/packages/auth-ui/src/VerifyEmailForm.tsx
new file mode 100644
index 00000000..fce07463
--- /dev/null
+++ b/packages/auth-ui/src/VerifyEmailForm.tsx
@@ -0,0 +1,117 @@
+import { useState, type FormEvent } from 'react';
+import type { VerifyEmailFormProps } from './types.js';
+
+/**
+ * Email verification form — 6-digit code input with resend option.
+ * Styled via CSS custom properties (inherits --bl-* from host app).
+ */
+export function VerifyEmailForm({
+ onSubmit,
+ onResend,
+ isLoading = false,
+ error,
+ success,
+ email,
+ className,
+}: VerifyEmailFormProps) {
+ const [code, setCode] = useState('');
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ onSubmit(code);
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/auth-ui/src/__tests__/new-components.test.tsx b/packages/auth-ui/src/__tests__/new-components.test.tsx
new file mode 100644
index 00000000..76526695
--- /dev/null
+++ b/packages/auth-ui/src/__tests__/new-components.test.tsx
@@ -0,0 +1,401 @@
+import { describe, expect, it, vi, beforeEach } from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { RegisterForm } from '../RegisterForm.js';
+import { ForgotPasswordForm } from '../ForgotPasswordForm.js';
+import { ResetPasswordForm } from '../ResetPasswordForm.js';
+import { VerifyEmailForm } from '../VerifyEmailForm.js';
+import { OnboardingShell } from '../OnboardingShell.js';
+import { AuthPageLayout } from '../AuthPageLayout.js';
+import { PasswordStrengthBar, getPasswordStrength } from '../PasswordStrengthBar.js';
+
+describe('RegisterForm', () => {
+ beforeEach(() => cleanup());
+
+ it('renders all fields', () => {
+ render();
+ expect(screen.getByTestId('bl-register-name')).toBeDefined();
+ expect(screen.getByTestId('bl-register-email')).toBeDefined();
+ expect(screen.getByTestId('bl-register-password')).toBeDefined();
+ expect(screen.getByTestId('bl-register-confirm')).toBeDefined();
+ expect(screen.getByTestId('bl-register-submit')).toBeDefined();
+ });
+
+ it('calls onSubmit with name, email, password', () => {
+ const onSubmit = vi.fn();
+ render();
+
+ fireEvent.change(screen.getByTestId('bl-register-name'), { target: { value: 'Alice' } });
+ fireEvent.change(screen.getByTestId('bl-register-email'), {
+ target: { value: 'alice@example.com' },
+ });
+ fireEvent.change(screen.getByTestId('bl-register-password'), {
+ target: { value: 'Password1!' },
+ });
+ fireEvent.change(screen.getByTestId('bl-register-confirm'), {
+ target: { value: 'Password1!' },
+ });
+ fireEvent.submit(screen.getByTestId('bl-register-submit').closest('form')!);
+
+ expect(onSubmit).toHaveBeenCalledWith({
+ name: 'Alice',
+ email: 'alice@example.com',
+ password: 'Password1!',
+ });
+ });
+
+ it('shows password mismatch error', () => {
+ render();
+ fireEvent.change(screen.getByTestId('bl-register-password'), {
+ target: { value: 'Password1!' },
+ });
+ fireEvent.change(screen.getByTestId('bl-register-confirm'), {
+ target: { value: 'Different1!' },
+ });
+
+ expect(screen.getByTestId('bl-register-mismatch')).toBeDefined();
+ expect(screen.getByText('Passwords do not match')).toBeDefined();
+ });
+
+ it('displays error message', () => {
+ render();
+ expect(screen.getByTestId('bl-register-error')).toBeDefined();
+ expect(screen.getByText('Email already taken')).toBeDefined();
+ });
+
+ it('shows terms checkbox when termsUrl provided', () => {
+ render();
+ expect(screen.getByTestId('bl-register-terms')).toBeDefined();
+ expect(screen.getByText('Terms of Service')).toBeDefined();
+ });
+
+ it('renders switch to login link', () => {
+ const onSwitch = vi.fn();
+ render();
+ const link = screen.getByTestId('bl-register-switch-login');
+ fireEvent.click(link);
+ expect(onSwitch).toHaveBeenCalledOnce();
+ });
+
+ it('shows loading state', () => {
+ render();
+ expect(screen.getByText('Creating account...')).toBeDefined();
+ });
+
+ it('shows password strength bar when typing', () => {
+ render();
+ fireEvent.change(screen.getByTestId('bl-register-password'), { target: { value: 'ab' } });
+ expect(screen.getByTestId('bl-password-strength')).toBeDefined();
+ });
+});
+
+describe('ForgotPasswordForm', () => {
+ beforeEach(() => cleanup());
+
+ it('renders email input and submit', () => {
+ render();
+ expect(screen.getByTestId('bl-forgot-email')).toBeDefined();
+ expect(screen.getByTestId('bl-forgot-submit')).toBeDefined();
+ });
+
+ it('calls onSubmit with email', () => {
+ const onSubmit = vi.fn();
+ render();
+ fireEvent.change(screen.getByTestId('bl-forgot-email'), {
+ target: { value: 'test@example.com' },
+ });
+ fireEvent.submit(screen.getByTestId('bl-forgot-submit').closest('form')!);
+ expect(onSubmit).toHaveBeenCalledWith('test@example.com');
+ });
+
+ it('displays error message', () => {
+ render();
+ expect(screen.getByTestId('bl-forgot-error')).toBeDefined();
+ });
+
+ it('displays success message', () => {
+ render();
+ expect(screen.getByTestId('bl-forgot-success')).toBeDefined();
+ expect(screen.getByText('Check your email')).toBeDefined();
+ });
+
+ it('renders back button and calls onBack', () => {
+ const onBack = vi.fn();
+ render();
+ fireEvent.click(screen.getByTestId('bl-forgot-back'));
+ expect(onBack).toHaveBeenCalledOnce();
+ });
+
+ it('shows loading state', () => {
+ render();
+ expect(screen.getByText('Sending...')).toBeDefined();
+ });
+});
+
+describe('ResetPasswordForm', () => {
+ beforeEach(() => cleanup());
+
+ it('renders password fields and submit', () => {
+ render();
+ expect(screen.getByTestId('bl-reset-password')).toBeDefined();
+ expect(screen.getByTestId('bl-reset-confirm')).toBeDefined();
+ expect(screen.getByTestId('bl-reset-submit')).toBeDefined();
+ });
+
+ it('calls onSubmit with password', () => {
+ const onSubmit = vi.fn();
+ render();
+ fireEvent.change(screen.getByTestId('bl-reset-password'), { target: { value: 'NewPass1!' } });
+ fireEvent.change(screen.getByTestId('bl-reset-confirm'), { target: { value: 'NewPass1!' } });
+ fireEvent.submit(screen.getByTestId('bl-reset-submit').closest('form')!);
+ expect(onSubmit).toHaveBeenCalledWith('NewPass1!');
+ });
+
+ it('shows mismatch error', () => {
+ render();
+ fireEvent.change(screen.getByTestId('bl-reset-password'), { target: { value: 'NewPass1!' } });
+ fireEvent.change(screen.getByTestId('bl-reset-confirm'), { target: { value: 'Different!' } });
+ expect(screen.getByTestId('bl-reset-mismatch')).toBeDefined();
+ });
+
+ it('displays error and success messages', () => {
+ const { rerender } = render();
+ expect(screen.getByTestId('bl-reset-error')).toBeDefined();
+
+ rerender();
+ expect(screen.getByTestId('bl-reset-success')).toBeDefined();
+ });
+
+ it('shows password strength bar', () => {
+ render();
+ fireEvent.change(screen.getByTestId('bl-reset-password'), { target: { value: 'abc' } });
+ expect(screen.getByTestId('bl-password-strength')).toBeDefined();
+ });
+});
+
+describe('VerifyEmailForm', () => {
+ beforeEach(() => cleanup());
+
+ it('renders code input and submit', () => {
+ render();
+ expect(screen.getByTestId('bl-verify-code')).toBeDefined();
+ expect(screen.getByTestId('bl-verify-submit')).toBeDefined();
+ });
+
+ it('calls onSubmit with code', () => {
+ const onSubmit = vi.fn();
+ render();
+ fireEvent.change(screen.getByTestId('bl-verify-code'), { target: { value: '123456' } });
+ fireEvent.submit(screen.getByTestId('bl-verify-submit').closest('form')!);
+ expect(onSubmit).toHaveBeenCalledWith('123456');
+ });
+
+ it('displays email address', () => {
+ render();
+ expect(screen.getByText('test@example.com')).toBeDefined();
+ });
+
+ it('renders resend button', () => {
+ const onResend = vi.fn();
+ render();
+ fireEvent.click(screen.getByTestId('bl-verify-resend'));
+ expect(onResend).toHaveBeenCalledOnce();
+ });
+
+ it('displays error and success messages', () => {
+ const { rerender } = render();
+ expect(screen.getByTestId('bl-verify-error')).toBeDefined();
+
+ rerender();
+ expect(screen.getByTestId('bl-verify-success')).toBeDefined();
+ });
+
+ it('strips non-numeric characters', () => {
+ render();
+ const input = screen.getByTestId('bl-verify-code');
+ fireEvent.change(input, { target: { value: 'abc123def456' } });
+ expect((input as unknown as { value: string }).value).toBe('123456');
+ });
+});
+
+describe('OnboardingShell', () => {
+ beforeEach(() => cleanup());
+
+ const steps = [
+ { key: 'welcome', label: 'Welcome' },
+ { key: 'profile', label: 'Profile' },
+ { key: 'preferences', label: 'Preferences' },
+ ];
+
+ it('renders steps and content', () => {
+ render(
+
+ Step 1 content
+
+ );
+ expect(screen.getByTestId('bl-onboarding-shell')).toBeDefined();
+ expect(screen.getByTestId('bl-onboarding-steps')).toBeDefined();
+ expect(screen.getByTestId('bl-onboarding-progress')).toBeDefined();
+ expect(screen.getByText('Step 1 content')).toBeDefined();
+ expect(screen.getByText('Welcome')).toBeDefined();
+ expect(screen.getByText('Profile')).toBeDefined();
+ });
+
+ it('disables Back on first step', () => {
+ render(
+
+
+
+ );
+ const back = screen.getByTestId('bl-onboarding-back');
+ expect(back.getAttribute('disabled')).toBe('');
+ });
+
+ it('calls onNext on middle step', () => {
+ const onNext = vi.fn();
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByTestId('bl-onboarding-next'));
+ expect(onNext).toHaveBeenCalledOnce();
+ });
+
+ it('shows Complete on last step and calls onComplete', () => {
+ const onComplete = vi.fn();
+ render(
+
+
+
+ );
+ const btn = screen.getByTestId('bl-onboarding-complete');
+ expect(btn.textContent).toBe('Complete');
+ fireEvent.click(btn);
+ expect(onComplete).toHaveBeenCalledOnce();
+ });
+
+ it('calls onBack on non-first step', () => {
+ const onBack = vi.fn();
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByTestId('bl-onboarding-back'));
+ expect(onBack).toHaveBeenCalledOnce();
+ });
+});
+
+describe('AuthPageLayout', () => {
+ beforeEach(() => cleanup());
+
+ it('renders product name and title', () => {
+ render(
+
+ Form content
+
+ );
+ expect(screen.getByTestId('bl-auth-product-name').textContent).toBe('TestApp');
+ expect(screen.getByTestId('bl-auth-title').textContent).toBe('Sign In');
+ expect(screen.getByText('Form content')).toBeDefined();
+ });
+
+ it('renders subtitle when provided', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByTestId('bl-auth-subtitle').textContent).toBe('Welcome back');
+ });
+
+ it('renders logo as element', () => {
+ render(
+ Logo}
+ >
+
+
+ );
+ expect(screen.getByTestId('custom-logo')).toBeDefined();
+ });
+
+ it('renders footer', () => {
+ render(
+ Footer text}>
+
+
+ );
+ expect(screen.getByTestId('bl-auth-footer')).toBeDefined();
+ expect(screen.getByText('Footer text')).toBeDefined();
+ });
+});
+
+describe('PasswordStrengthBar', () => {
+ beforeEach(() => cleanup());
+
+ it('returns null for empty password', () => {
+ const { container } = render();
+ expect(container.querySelector('[data-testid="bl-password-strength"]')).toBeNull();
+ });
+
+ it('shows Weak for short password', () => {
+ render();
+ expect(screen.getByTestId('bl-password-strength-label').textContent).toBe('Weak');
+ });
+
+ it('shows Strong for complex password', () => {
+ render();
+ expect(screen.getByTestId('bl-password-strength-label').textContent).toBe('Strong');
+ });
+});
+
+describe('getPasswordStrength', () => {
+ it('returns weak for very short passwords', () => {
+ expect(getPasswordStrength('ab')).toBe('weak');
+ });
+
+ it('returns fair for medium passwords', () => {
+ expect(getPasswordStrength('abcdefgh1')).toBe('fair');
+ });
+
+ it('returns good for decent passwords', () => {
+ expect(getPasswordStrength('Abcdefgh1')).toBe('good');
+ });
+
+ it('returns strong for complex passwords', () => {
+ expect(getPasswordStrength('MyStr0ng!Pass')).toBe('strong');
+ });
+});
diff --git a/packages/auth-ui/src/index.ts b/packages/auth-ui/src/index.ts
index 9803e793..e24f8748 100644
--- a/packages/auth-ui/src/index.ts
+++ b/packages/auth-ui/src/index.ts
@@ -1,9 +1,24 @@
export { LoginForm } from './LoginForm.js';
+export { RegisterForm } from './RegisterForm.js';
+export { ForgotPasswordForm } from './ForgotPasswordForm.js';
+export { ResetPasswordForm } from './ResetPasswordForm.js';
+export { VerifyEmailForm } from './VerifyEmailForm.js';
export { MfaChallenge } from './MfaChallenge.js';
export { SocialButtons } from './SocialButtons.js';
+export { OnboardingShell } from './OnboardingShell.js';
+export { AuthPageLayout } from './AuthPageLayout.js';
+export { PasswordStrengthBar, getPasswordStrength } from './PasswordStrengthBar.js';
export type {
LoginFormProps,
+ RegisterFormProps,
+ ForgotPasswordFormProps,
+ ResetPasswordFormProps,
+ VerifyEmailFormProps,
MfaChallengeProps,
SocialButtonsProps,
SocialProvider,
+ OnboardingShellProps,
+ OnboardingStep,
+ AuthPageLayoutProps,
+ PasswordStrength,
} from './types.js';
diff --git a/packages/auth-ui/src/types.ts b/packages/auth-ui/src/types.ts
index db3f9bd5..706f44fd 100644
--- a/packages/auth-ui/src/types.ts
+++ b/packages/auth-ui/src/types.ts
@@ -40,3 +40,108 @@ export interface SocialButtonsProps {
/** Additional CSS class for the root element. */
className?: string;
}
+
+export interface RegisterFormProps {
+ /** Called when user submits registration. */
+ onSubmit: (data: { name: string; email: string; password: string }) => void;
+ /** Whether the form is currently loading. */
+ isLoading?: boolean;
+ /** Error message to display. */
+ error?: string | null;
+ /** Terms of service URL (renders checkbox if provided). */
+ termsUrl?: string;
+ /** Privacy policy URL. */
+ privacyUrl?: string;
+ /** Called when user clicks "Already have an account?" */
+ onSwitchToLogin?: () => void;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export interface ForgotPasswordFormProps {
+ /** Called when user submits email for password reset. */
+ onSubmit: (email: string) => void;
+ /** Whether the form is currently loading. */
+ isLoading?: boolean;
+ /** Error message to display. */
+ error?: string | null;
+ /** Success message (e.g., "Check your email"). */
+ success?: string | null;
+ /** Called when user clicks "Back to login". */
+ onBack?: () => void;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export interface ResetPasswordFormProps {
+ /** Called when user submits new password. */
+ onSubmit: (password: string) => void;
+ /** Whether the form is currently loading. */
+ isLoading?: boolean;
+ /** Error message to display. */
+ error?: string | null;
+ /** Success message (e.g., "Password updated"). */
+ success?: string | null;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export interface VerifyEmailFormProps {
+ /** Called when user submits the verification code. */
+ onSubmit: (code: string) => void;
+ /** Called when user clicks "Resend code". */
+ onResend?: () => void;
+ /** Whether the form is currently loading. */
+ isLoading?: boolean;
+ /** Error message to display. */
+ error?: string | null;
+ /** Success message (e.g., "Code resent"). */
+ success?: string | null;
+ /** Email address being verified (for display). */
+ email?: string;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export interface OnboardingStep {
+ /** Unique key for the step. */
+ key: string;
+ /** Display label for the step indicator. */
+ label: string;
+}
+
+export interface OnboardingShellProps {
+ /** Ordered list of steps. */
+ steps: OnboardingStep[];
+ /** Index of the current step (0-based). */
+ currentStep: number;
+ /** Called when user clicks Next. */
+ onNext: () => void;
+ /** Called when user clicks Back. */
+ onBack: () => void;
+ /** Called when the final step completes. */
+ onComplete: () => void;
+ /** Content to render for the current step. */
+ children: React.ReactNode;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export interface AuthPageLayoutProps {
+ /** Product name displayed at the top. */
+ productName: string;
+ /** Optional logo element or URL. */
+ logo?: React.ReactNode;
+ /** Page title (e.g., "Sign In", "Create Account"). */
+ title: string;
+ /** Subtitle or description. */
+ subtitle?: string;
+ /** Form content. */
+ children: React.ReactNode;
+ /** Footer content (links, etc.). */
+ footer?: React.ReactNode;
+ /** Additional CSS class for the root element. */
+ className?: string;
+}
+
+export type PasswordStrength = 'weak' | 'fair' | 'good' | 'strong';