Documentation Index
Fetch the complete documentation index at: https://docs.streambird.io/llms.txt
Use this file to discover all available pages before exploring further.
A full-featured authentication component with email OTP, including error handling, loading states, and user experience improvements.
Implementation
import { useState } from 'react';
import { useLoginWithEmail, useMoonKey } from '@moon-key/react-auth';
export default function AuthenticationFlow() {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [codeSent, setCodeSent] = useState(false);
const [error, setError] = useState('');
const { sendCode, loginWithCode, state } = useLoginWithEmail();
const { user, isAuthenticated, ready } = useMoonKey();
// Wait for MoonKey to initialize
if (!ready) {
return (
<div className="loading-container">
<div className="spinner" />
<p>Loading MoonKey...</p>
</div>
);
}
// Show user profile if authenticated
if (isAuthenticated) {
return (
<div className="profile-container">
<h2>Welcome back!</h2>
<p>Email: {user?.email?.address}</p>
<p>User ID: {user?.id}</p>
{user?.wallet && <p>Wallet: {user.wallet.address}</p>}
</div>
);
}
const handleSendCode = async () => {
setError('');
try {
await sendCode({ email });
setCodeSent(true);
} catch (err) {
setError('Failed to send code. Please check your email and try again.');
console.error(err);
}
};
const handleLogin = async () => {
setError('');
try {
await loginWithCode({ code });
// User is now authenticated - component will re-render
} catch (err) {
setError('Invalid code. Please try again or request a new code.');
console.error(err);
}
};
const handleResendCode = async () => {
setCode('');
setCodeSent(false);
setError('');
};
if (!codeSent) {
return (
<div className="auth-container">
<h1>Log in to continue</h1>
<p>Enter your email to receive a verification code</p>
{error && <div className="error-message">{error}</div>}
<input
type="email"
placeholder="email@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={state === 'sending'}
onKeyDown={(e) => e.key === 'Enter' && handleSendCode()}
/>
<button
onClick={handleSendCode}
disabled={!email || state === 'sending'}
className="primary-button"
>
{state === 'sending' ? 'Sending code...' : 'Continue with Email'}
</button>
</div>
);
}
return (
<div className="auth-container">
<h1>Check your email</h1>
<p>We sent a 6-digit code to <strong>{email}</strong></p>
{error && <div className="error-message">{error}</div>}
<input
type="text"
placeholder="000000"
value={code}
onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
maxLength={6}
disabled={state === 'logging-in'}
autoFocus
onKeyDown={(e) => e.key === 'Enter' && code.length === 6 && handleLogin()}
/>
<button
onClick={handleLogin}
disabled={code.length !== 6 || state === 'logging-in'}
className="primary-button"
>
{state === 'logging-in' ? 'Verifying...' : 'Verify Code'}
</button>
<button onClick={handleResendCode} className="secondary-button">
Use different email
</button>
</div>
);
}
Key features
- Loading states - Shows spinner while MoonKey initializes
- Error handling - Clear error messages for common issues
- Email validation - Disables button when email is empty
- Code formatting - Automatically strips non-numeric characters
- Keyboard support - Press Enter to submit forms
- State management - Tracks flow state (sending, logging-in)
- User feedback - Shows appropriate messages at each step
Next steps
Styling Examples
Add CSS styles to this component
Protected Routes
Protect routes with authentication