Simulating a login for the React Chinook project

Note: I need to make a video that explains some of the components, such as the Login and the NavBar

Add the users collection to the chinook-data.json file:

"users":[
    {"id":1, "email":"admin@chinookcorp.com", "password":"test123", "roleId": 1},
    {"id":2, "email":"employee@chinookcorp.com", "password":"test123", "roleId": 2},
    {"id":3, "email":"customer@example.com", "password":"test123", "roleId": 3}
  ]

Add login-data-access.js to the api folder:

import axios from "axios"; 

const baseURL = "http://localhost:8080/users/";

export function login(email, password){
  const loginURL = baseURL + `?email=${email}&password=${password}`;
  // The json server appears to return a user that matches the email
  // even if the password doesn't match, so I had to put the logic in the IF statement
	return axios.get(loginURL)
    .then(resp => {
      if(resp.data.length === 1 && resp.data[0].password === password){
        return resp.data[0];
      }else{
        throw new Error("Unable to login")
      }
    })
    .catch(errorHandler);
}

function errorHandler(err){
  console.log("ERROR (in login-data-access):", err.message);
  throw err;
}

Create UserContext.js in the src folder:

import {createContext} from 'react'
export const UserContext = createContext(null);

Add the Login.js (or Login.jsx if you are using Vite) component to the pages folder (this started out as the same Login component that we created for the React Sample Project - the changes are noted with comments):

import React,{useRef, useEffect, useState, useContext} from 'react' // Import useContext
import {login} from '../api/login-data-access'; // Import the login function
import { UserContext } from '../UserContext'; // import the UserContext
import { useNavigate } from 'react-router-dom'; 

const Login = () => {

  const userNameRef = useRef(); 
  const userPasswordRef = useRef();

  const [errors, setErrors] = useState({}); 

  const navigate = useNavigate();
	const {setCurrentUser} = useContext(UserContext); // get the UserContext (the currentUser state that is declared in App.js)
  
  useEffect(() => {
    setCurrentUser(null); // reset the current user (by coming to this page, it effectively logs out the current user)
    userNameRef.current.focus(); 
  }, [])
  
  

  async function handleSubmit(evt){ // Make sure to declare handleSubmit as 'async'
    evt.preventDefault();
    
    const userName = userNameRef.current.value;
    const userPassword = userPasswordRef.current.value;

    if(validate(userName, userPassword)){
      
      // Replaced the console log we had here with this try/catch block:
      try{
        const user = await login(userName, userPassword);
        console.log(user);
        setCurrentUser(user);
        navigate("/");
      }catch(e){
        console.log("Error in Login.js", e)
        setErrors({loginFailed:"Invalid Login"})
      }

    }
    
  }

  function validate(userName, userPassword){
    let isValid = true;
    setErrors({}); 

    if(!userName){
      //setErrors({...errors, userName:"You must enter a user name"});
      setErrors(prev => ({...prev, userName:"You must enter a user name"}))
      isValid = false;
    }

    if(!userPassword){
      //setErrors({...errors, userPassword:"You must enter a password"});
      setErrors(prev => ({...prev, userPassword:"You must enter a password"}))
      isValid = false;
    }

    return isValid;
  }

  return (
    <form onSubmit={handleSubmit}>
      {errors.loginFailed && <span className="text-danger">{errors.loginFailed}</span>} {/* Added this for failed login attempts */}
      <br />
      <label>User Name</label>
      <br/>
      <input type="text" ref={userNameRef}  />
      {errors.userName && <span className="text-danger">{errors.userName}</span>}
      <br/>
      <label>Password</label>
      <br/>
      <input type="password" ref={userPasswordRef} />
      {errors.userPassword && <span className="text-danger">{errors.userPassword}</span>}
      <br/>
      <button>Log In</button>
      
    </form>
  )
}

export default Login

Update App.js (or App.jsx if you are using Vite) to 'provide' the UserContext:

import { Routes, Route } from "react-router-dom";
import { useState } from "react";                          // Import useState
import HomePage from "./pages/HomePage";
import ArtistPage from "./pages/artists/ArtistPage";
import GenrePage from "./pages/genres/GenrePage";
import MediaTypePage from "./pages/media-types/MediaTypePage";
import AlbumPage from "./pages/albums/AlbumPage";
import TrackPage from "./pages/tracks/TrackPage";
import Login from "./pages/Login";                         // Import the Login component
import { UserContext } from './UserContext';               // Import the UserContext

function App() {

  const [currentUser, setCurrentUser] = useState(null);    // Add the currentUser state

  return (
    <UserContext.Provider value={{currentUser, setCurrentUser}}>   {/* Wrap the routes with UserContext.Provider and set the value*/}
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/artists/*" element={<ArtistPage />} />
        <Route path="/genres/*" element={<GenrePage />} />
        <Route path="/media-types/*" element={<MediaTypePage />} />
        <Route path="/albums/*" element={<AlbumPage />} />
        <Route path="/tracks/*" element={<TrackPage />} />
        <Route path="/login" element={<Login />} />                {/* Add a route to the login page*/}
      </Routes>
    </UserContext.Provider>                                        {/* Don't forget the closing tag!*/}
  );
}

export default App;

Update NavBar.js:

import { NavLink } from "react-router-dom"
import { useRef, useContext } from "react";   // import useContext
import { Collapse } from "bootstrap";
import { UserContext } from '../UserContext';   // import the UserContext

const NavBar = () => {

  const {currentUser} = useContext(UserContext); // set the currentUser from the context
  

  const menu = useRef();

  function hideMenu(){
    if(menu.current.classList.contains("show")){
      Collapse.getInstance(menu.current).hide();
    }
  }

  return (
    <nav className="navbar navbar-expand-md navbar-dark bg-dark container-fluid px-2">
            
      <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
        <span className="navbar-toggler-icon"></span>
      </button>

      <div className="collapse navbar-collapse" ref={menu} id="navbarCollapse">
        <ul className="navbar-nav me-auto mb-2 mb-md-0">
          <li className="nav-item">
            <NavLink className="nav-link" onClick={hideMenu} to="/">Home</NavLink>
          </li>

          {/* The ternary operator is used for conditional rendering - if there is a current user logged in, show all the links*/}
          {
          currentUser 
            ?
            <>
              <li className="nav-item">
                <NavLink className="nav-link" onClick={hideMenu} to="/artists">Artists</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" onClick={hideMenu} to="/genres">Genres</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" onClick={hideMenu} to="/media-types">Media Types</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" onClick={hideMenu} to="/albums">Albums</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" onClick={hideMenu} to="/tracks">Tracks</NavLink>
              </li>
              <li className="nav-item">
                {/* Added this link to log out (takes you to the login component, which clears the currentUser*/}
                <NavLink className="nav-link" onClick={hideMenu} to="/login">Log out</NavLink>
              </li>
            </>
          :
            <li className="nav-item">
              <NavLink className="nav-link" onClick={hideMenu} to="/login">Login</NavLink> {/* Added this link to log in (only shows if the currentUser is NOT logged in) */}
            </li>
          }
        </ul>
      </div>

      {/* Display the currentUser's email if the currentUser is not null */}
      <span className="text-white">{currentUser?.email}</span>
    </nav>
  )
}

export default NavBar