Skip to main content
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