Login Form and Auth Context

The Plan

The ShopEase frontend is a React 18 app (Vite) on http://localhost:5173; the API is on http://localhost:8080. We need: a place that knows who is logged in (a React context), a login page, a way to attach the token to requests, and routes that only logged-in users can open. React basics are covered in the React material.

src/
  api.js               axios instance + interceptors
  auth/AuthContext.jsx user, login(), logout()
  auth/ProtectedRoute.jsx
  pages/LoginPage.jsx
  pages/OrdersPage.jsx
  App.jsx              routes

AuthContext

// src/auth/AuthContext.jsx
import { createContext, useContext, useState } from 'react';
import { api, tokenStore } from '../api';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(() => tokenStore.getUser());

  async function login(email, password) {
    const { data } = await api.post('/auth/login', { email, password });
    tokenStore.save(data.accessToken, data.user);
    setUser(data.user);
    return data.user;
  }

  function logout() {
    tokenStore.clear();
    setUser(null);
  }

  const hasRole = (role) => !!user && user.roles.includes(role);

  return (
    <AuthContext.Provider value={{ user, login, logout, hasRole }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}

Login Page

// src/pages/LoginPage.jsx
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useAuth } from '../auth/AuthContext';

export default function LoginPage() {
  const { login } = useAuth();
  const navigate = useNavigate();
  const location = useLocation();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setError('');
    setLoading(true);
    try {
      await login(email, password);
      const goTo = location.state?.from?.pathname || '/orders';
      navigate(goTo, { replace: true });
    } catch (err) {
      setError(err.response?.status === 401 ? 'Invalid email or password' : 'Something went wrong');
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} className="login-form">
      <h2>Login to ShopEase</h2>
      <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" required />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required />
      {error && <p className="error">{error}</p>}
      <button type="submit" disabled={loading}>{loading ? 'Logging in...' : 'Login'}</button>
    </form>
  );
}