Building a Comprehensive User Authentication Flow with React and Node.js (Part 2)

In this blog post, we’re delving into the front-end setup for our crypto exchange platform’s user authentication. Following the backend setup we explored earlier, let’s now build a user-friendly, responsive React interface that includes registration, login, password reset, and a wallet management feature.

Front-End Development with React

Prerequisites

Ensure that you’ve already:

  • Created a new React application (npx create-react-app mycrypt).

  • Installed essential dependencies for styling and icons (e.g., react-router-dom, axios, and react-icons).

Step 1: Folder and Component Structure

Our front-end app will consist of multiple components, each handling a specific part of the authentication flow. Components like RegisterPage, LoginPage, ForgotPasswordPage, and ResetPasswordPage are organized separately, with each containing its own styles and form handling logic.

Setting up Routes

Inside Routes.js, define the application routes with useRoutes. This enables easy navigation and helps in setting up secure routes for protected pages such as the Wallet.

import React from 'react';
import { useRoutes } from 'react-router-dom';
import NotFound from './NotFound';
import ForgotPasswordPage from './ForgotPasswordPage';
import LoginPage from './LoginPage';
import RegisterPage from './RegisterPage';
import WalletScreen from './WalletScreen';
import SwapScreen from './SwapScreen';
import ResetPasswordPage from './ResetPasswordPage';
import SuccessPage from './SuccessPage';


const Routes = () => {
    const element = useRoutes([

        {
            children: [
                {
                    path: '/',
                    element: <LoginPage />
                },
                {
                    path: '/register',
                    element: <RegisterPage />
                },
                {
                    path: '/forgot',
                    element: <ForgotPasswordPage />
                },
                {
                    path: '/reset/:token', // Update this to accept a token parameter
                    element: <ResetPasswordPage />
                },
                {
                    path: '/success',
                    element: <SuccessPage />
                },

                {
                    path: '/wallet',
                    element: <WalletScreen />
                },
                {
                    path: '/swap',
                    element: <SwapScreen />
                },
                {
                    path: '*',
                    element: <NotFound />
                },
            ]
        }
    ])

    return element
}
export default Routes

Step 2: Creating Registration and Login Pages

Each page component (e.g., RegisterPage, LoginPage) uses form validation and Axios to communicate with the backend. For example, the RegisterPage component collects email, password, and confirmation fields, with validation to ensure that passwords match. Error and success messages provide feedback, enhancing the user experience.

LOGIN

import React, { useState } from 'react';
import { FaBitcoin } from 'react-icons/fa';
import '../css/LoginPage.css';
import { Link, useNavigate } from 'react-router-dom';
import websiteConstants from '../components/websiteConstants';
import axios from 'axios';

const LoginPage = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errorMessage, setErrorMessage] = useState('');
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    setErrorMessage('');

    try {
      const response = await axios.post('http://localhost:5000/login', {
        email,
        password,
      }, { withCredentials: true });


      if (response.status === 200) {
        navigate('/wallet');
      }
    } catch (error) {
      if (error.response && error.response.data && error.response.data.message) {
        setErrorMessage(error.response.data.message);
      } else {
        setErrorMessage('An error occurred during login. Please try again.');
      }
    }
  };

  return (
    <div className="login-page">
      <div className="login-container">
        <div className="crypto-icon">
          <FaBitcoin size={50} color="#00ffcc" />
        </div>
        <h2>Login to {websiteConstants.WEBSITE_NAME}</h2>
        <form onSubmit={handleLogin} className="login-form">
          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          {errorMessage && <p className="error-text">{errorMessage}</p>}
          <button type="submit" className="login-button">
            Login
          </button>
        </form>
        <p className="link-text">
          Don't have an account? <Link to="/register">Sign Up</Link>
        </p>
        <p className="link-text">
          Forgot your password? <Link to="/forgot">Reset Password</Link>
        </p>
      </div>
    </div>
  );
};

export default LoginPage;

Register

import React, { useState } from 'react';
import { FaBitcoin } from 'react-icons/fa';
import '../css/LoginPage.css';
import { Link, useNavigate } from 'react-router-dom';
import websiteConstants from '../components/websiteConstants';
import axios from 'axios';

const RegisterPage = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');
  const navigate = useNavigate();

  const handleSignup = async (e) => {
    e.preventDefault();
    setErrorMessage('');
    setSuccessMessage('');

    try {
      const response = await axios.post('http://localhost:5000/register', {
  email,
  password,
  confirmPassword,
}, { withCredentials: true });


      if (response.status === 201) {
        setSuccessMessage('User registered successfully!');
        setTimeout(() => {
          navigate('/wallet');
        }, 2000); // Redirect to wallet page after 2 seconds
      }
    } catch (error) {
      if (error.response && error.response.data && error.response.data.message) {
        setErrorMessage(error.response.data.message);
      } else {
        setErrorMessage('An error occurred during signup. Please try again.');
      }
    }
  };

  return (
    <div className="login-page">
      <div className="login-container">
        <div className="crypto-icon">
          <FaBitcoin size={50} color="#00ffcc" />
        </div>
        <h2>Sign Up for {websiteConstants.WEBSITE_NAME}</h2>
        <form onSubmit={handleSignup} className="login-form">
          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label htmlFor="confirmPassword">Confirm Password</label>
            <input
              type="password"
              id="confirmPassword"
              value={confirmPassword}
              onChange={(e) => setConfirmPassword(e.target.value)}
              required
            />
          </div>
          {errorMessage && <p className="error-text">{errorMessage}</p>}
          {successMessage && <p className="success-text">{successMessage}</p>}
          <button type="submit" className="login-button">
            Sign Up
          </button>
        </form>
        <p className="link-text">
          Already have an account? <Link to="/">Login</Link>
        </p>
      </div>
    </div>
  );
};

export default RegisterPage;

Step 3: Forgot and Reset Password Components

The ForgotPasswordPage allows users to request a password reset link, while ResetPasswordPage enables users to update their password securely by validating the reset token.

In ForgotPasswordPage, an Axios POST request sends the user’s email to the backend, which, if valid, generates a reset token.

import React, { useState } from 'react';
import { FaBitcoin } from 'react-icons/fa';
import '../css/LoginPage.css';
import { Link } from 'react-router-dom';
import axios from 'axios';

const ForgotPasswordPage = () => {
  const [email, setEmail] = useState('');
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');

  const handleForgotPassword = async (e) => {
    e.preventDefault();
    setErrorMessage('');
    setSuccessMessage('');

    try {
      console.log('Sending forgot password request for:', email);
      const response = await axios.post('http://localhost:5000/forgot', { email }, { withCredentials: true });
      console.log('Forgot password response:', response);
      if (response.status === 200) {
        setSuccessMessage('Password reset link sent to your email');
      }
    } catch (error) {
      console.log('Forgot password error response:', error);
      if (error.response && error.response.data && error.response.data.message) {
        setErrorMessage(error.response.data.message);
      } else {
        setErrorMessage('An error occurred. Please try again.');
      }
    }
  };

  return (
    <div className="login-page">
      <div className="login-container">
        <div className="crypto-icon">
          <FaBitcoin size={50} color="#00ffcc" />
        </div>
        <h2>Forgot Password</h2>
        <form onSubmit={handleForgotPassword} className="login-form">
          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>
          {errorMessage && <p className="error-text">{errorMessage}</p>}
          {successMessage && <p className="success-text">{successMessage}</p>}
          <button type="submit" className="login-button">
            Reset Password
          </button>
        </form>
        <p className="link-text">
          Remembered your password? <Link to="/">Login</Link>
        </p>
      </div>
    </div>
  );
};

export default ForgotPasswordPage;
import React, { useState } from 'react';
import { FaBitcoin } from 'react-icons/fa';
import '../css/LoginPage.css';
import { useParams, useNavigate, Link } from 'react-router-dom';
import axios from 'axios';

const ResetPasswordPage = () => {
  const { token } = useParams(); // Extract the token from the URL
  const navigate = useNavigate(); // Hook to navigate programmatically
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');

  const handleResetPassword = async (e) => {
    e.preventDefault();
    setErrorMessage('');
    setSuccessMessage('');

    if (!token) {
      setErrorMessage('Invalid or expired reset token');
      return;
    }

    try {
      const response = await axios.post(`http://localhost:5000/reset/${token}`, {
        password,
        confirmPassword,
      });

      if (response.status === 200) {
        setSuccessMessage('Password updated successfully');
        setTimeout(() => {
          navigate('/success'); // Redirect to the success page after a short delay
        }, 2000);
      }
    } catch (error) {
      if (error.response && error.response.data && error.response.data.message) {
        setErrorMessage(error.response.data.message);
      } else {
        setErrorMessage('An error occurred. Please try again.');
      }
    }
  };

  return (
    <div className="login-page">
      <div className="login-container">
        <div className="crypto-icon">
          <FaBitcoin size={50} color="#00ffcc" />
        </div>
        <h2>Reset Password</h2>
        <form onSubmit={handleResetPassword} className="login-form">
          <div className="form-group">
            <label htmlFor="password">New Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label htmlFor="confirmPassword">Confirm New Password</label>
            <input
              type="password"
              id="confirmPassword"
              value={confirmPassword}
              onChange={(e) => setConfirmPassword(e.target.value)}
              required
            />
          </div>
          {errorMessage && <p className="error-text">{errorMessage}</p>}
          {successMessage && <p className="success-text">{successMessage}</p>}
          <button type="submit" className="login-button">
            Update Password
          </button>
        </form>
        <p className="link-text">
          Remembered your password? <Link to="/">Login</Link>
        </p>
      </div>
    </div>
  );
};

export default ResetPasswordPage;
import React from 'react';
import { FaBitcoin } from 'react-icons/fa';
import '../css/LoginPage.css';

const SuccessPage = () => {
  return (
    <div className="login-page">
      <div className="login-container">
        <div className="crypto-icon">
          <FaBitcoin size={50} color="#00ffcc" />
        </div>
        <h2>Success!</h2>
        <p>Your action was completed successfully.</p>
        <button className="login-button" onClick={() => window.location.href = '/'}>
          Go to Login
        </button>
      </div>
    </div>
  );
};

export default SuccessPage;

Step 4: Wallet Management Interface

The WalletScreen component displays the user’s cryptocurrency balance and integrates with the logout endpoint to securely end user sessions.

Inside the WalletScreen, action buttons (e.g., Send, Receive, Buy) and bottom navigation tabs enable seamless navigation within the wallet section.

import React, { useEffect, useState } from 'react';
import '../css/WalletScreen.css';
import { IconButton, Button } from '@mui/material';
import SendIcon from '@mui/icons-material/Send';
import DownloadIcon from '@mui/icons-material/Download';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import AttachMoneyIcon from '@mui/icons-material/AttachMoney';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
import ExploreIcon from '@mui/icons-material/Explore';
import LanguageIcon from '@mui/icons-material/Language';
import SettingsIcon from '@mui/icons-material/Settings';
import { NavLink, useNavigate } from 'react-router-dom';
import axios from 'axios';

const WalletScreen = () => {
  const [user, setUser] = useState(null);
  const navigate = useNavigate();

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await axios.get('http://localhost:5000/user', { withCredentials: true });
        if (response.status === 200) {
          setUser(response.data.user);
        } else {
          navigate('/');
        }
      } catch (error) {
        navigate('/');
      }
    };

    fetchUser();
  }, [navigate]);

  // Handle logout
  const handleLogout = async () => {
    try {
      const response = await axios.post('http://localhost:5000/logout', {}, { withCredentials: true });
      if (response.status === 200) {
        navigate('/');
      }
    } catch (error) {
      console.error('Logout failed:', error);
    }
  };

  const coins = [
    { name: 'BTC', price: '$66,844.30', change: '-1.71%', icon: <AttachMoneyIcon />, balance: '0' },
    { name: 'ETH', price: '$2,451.09', change: '-2.87%', icon: <AttachMoneyIcon />, balance: '0' },
    { name: 'TRX', price: '$0.16', change: '-1.25%', icon: <AttachMoneyIcon />, balance: '0' },
    { name: 'BNB', price: '$578.05', change: '-2.95%', icon: <AttachMoneyIcon />, balance: '0' },
  ];

  return (
    <div className="wallet-screen">
      {/* Top Balance Section */}
      <div className="balance-section">
        <h2>$0.00</h2>
        <p>Main Wallet</p>
        {user && (
          <div>
            <p>Welcome, {user.email}</p>
            <Button variant="contained" color="secondary" onClick={handleLogout}>
              Logout
            </Button>
          </div>
        )}
      </div>

      {/* Action Buttons */}
      <div className="action-buttons">
        {[
          { action: 'Send', icon: <SendIcon /> },
          { action: 'Receive', icon: <DownloadIcon /> },
          { action: 'Buy', icon: <ShoppingCartIcon /> },
          { action: 'Earn', icon: <AttachMoneyIcon /> },
        ].map((item, index) => (
          <div key={index} className="action-button">
            <IconButton style={{ backgroundColor: '#007bff', color: '#fff' }}>
              {item.icon}
            </IconButton>
            <p>{item.action}</p>
          </div>
        ))}
      </div>

      {/* Coins/NFTs Toggle */}
      <div className="toggle-section">
        <button className="toggle-button active">Coins</button>
        <button className="toggle-button">NFTs</button>
      </div>

      {/* Coin List */}
      <div className="coin-list">
        {coins.map((coin, index) => (
          <div key={index} className="coin-item">
            <div className="coin-icon">
              {coin.icon}
            </div>
            <div className="coin-details">
              <span className="coin-name">{coin.name}</span>
              <span className={`coin-price ${coin.change.startsWith('-') ? 'negative' : 'positive'}`}>
                {coin.price} {coin.change}
              </span>
            </div>
            <span className="coin-balance">{coin.balance}</span>
          </div>
        ))}
      </div>

      {/* Bottom Navigation */}
      <div className="bottom-navigation">
        {[
          { label: 'Wallet', icon: <AccountBalanceWalletIcon />, path: '/' },
          { label: 'Swap', icon: <SwapHorizIcon />, path: '/swap' },
          { label: 'Discover', icon: <ExploreIcon />, path: '/discover' },
          { label: 'Browser', icon: <LanguageIcon />, path: '/browser' },
          { label: 'Settings', icon: <SettingsIcon />, path: '/settings' },
        ].map((tab, index) => (
          <NavLink 
            to={tab.path} 
            key={index} 
            className="nav-item" 
            activeClassName="active-link"
          >
            {tab.icon}
            <p>{tab.label}</p>
          </NavLink>
        ))}
      </div>
    </div>
  );
};

export default WalletScreen;

Step 5: Styling and Icons

The styling is managed through CSS files (LoginPage.css, WalletScreen.css), with color consistency, intuitive layouts, and interactive icons (react-icons). Icons provide a familiar and user-friendly interface, particularly helpful for new users.

Summary

In this guide, we’ve completed the front-end of our authentication system, focusing on:

  • Registration and Login: Secure sign-up and login forms with real-time feedback.

  • Password Management: User-friendly forgot and reset password flows.

  • Wallet Interface: An interactive, easy-to-navigate wallet page with cryptocurrency balances and action buttons.

This full-stack project provides a solid foundation for any authentication flow in a modern web application, complete with the best practices for usability, security, and responsive design. The next steps could include adding email verification, multi-factor authentication, and integrating OAuth for even stronger security and user convenience.