Skip to main content

Overview

EducaStream features a comprehensive cart and checkout system that allows students to add multiple courses to their cart and complete purchases through Stripe payment integration. The cart state is managed globally and persisted in localStorage.

Cart State Management

The cart system uses React Context and Reducers to manage state across the application.

Cart Context

The CartContext provides global access to cart state and dispatch methods:
const CartContext = createContext();

const initialState = {
  cart: JSON.parse(localStorage.getItem("cart")) || [],
};
Location: src/context/CartContext.jsx:6-10

Cart Actions

The cart supports three main actions:
ADD_TO_CART
action
Adds a course to the cart with duplicate checking and purchase validation
REMOVE_FROM_CART
action
Removes a specific course from the cart by ID
CLEAR
action
Clears the entire cart (used after successful checkout)

Adding to Cart

When adding a course, the system validates:
  • Duplicate prevention: Checks if course is already in cart
  • Purchase history: Verifies user hasn’t already purchased the course
  • Toast notifications: Provides user feedback for each scenario
case ADD_TO_CART:
  const isCourseInCart = state.cart.find(
    (product) => product.id === action.payload.id
  );

  if (isCourseInCart) {
    Toast.fire({
      icon: "warning",
      title: "¡El curso ya existe en carrito!",
    });
    return state;
  }

  const session = JSON.parse(localStorage.getItem("userOnSession"));
  const userCourse = (session?.Payments || []).find((payment) =>
    (payment.Courses || []).find(
      (course) => course.id === action.payload.id
    )
  );

  if (userCourse) {
    Toast.fire({
      icon: "warning",
      title: "¡Ya has comprado este curso!",
    });
    return state;
  }

  Toast.fire({
    icon: "success",
    title: "¡Curso agregado al carrito con éxito!",
  });

  return {
    ...state,
    cart: [...state.cart, action.payload],
  };
Location: src/context/CartContext.jsx:26-71

Cart UI Component

The cart icon in the navigation displays a hover modal with cart contents.

Cart Modal Features

  • Hover activation: Modal appears on mouse enter
  • Item listing: Shows all courses with names and prices
  • Remove functionality: Each item can be removed individually
  • Total calculation: Real-time price total
  • Cart badge: Shows number of items in cart
const handleMouseEnter = () => {
  setIsModalOpen(true);
};

const handleMouseLeave = () => {
  setIsModalOpen(false);
};

useEffect(() => {
  const totalReducer = state.cart.reduce(
    (acc, product) => acc + product.price,
    0
  );
  const total = totalReducer.toFixed(2);
  setTotalPrice(total);
}, [state.cart]);
Location: src/Components/Cart/Cart.jsx:50-65

Cart Page

The full cart page provides a detailed view of items and checkout functionality.

Page Structure

1

Product List

Displays all cart items with images, names, and remove buttons. If cart is empty, shows a helpful message with link to courses.
2

Purchase Summary

Shows itemized list of courses and prices with calculated total.
3

Action Buttons

“Volver” (Back) button to return to courses and “Finalizar Compra” (Complete Purchase) button to proceed to payment.

Remove from Cart

Removing items requires confirmation using SweetAlert2:
const handleRemoveFromCart = (productId) => {
  Swal.fire({
    title: "¿Seguro que quieres eliminar el curso?",
    icon: "warning",
    showCancelButton: true,
    confirmButtonColor: "#d33",
    cancelButtonColor: "#3d0dca",
    cancelButtonText: "Cancelar",
    confirmButtonText: "Aceptar",
  }).then((result) => {
    if (result.isConfirmed) {
      Swal.fire({
        title: "El curso fue eliminado del carrito",
        icon: "success",
      });
      dispatch({ type: "REMOVE_FROM_CART", payload: productId });
    }
  });
};
Location: src/views/CartPage/CartPage.jsx:14-38

Total Calculation

useEffect(() => {
  const totalReducer = state.cart.reduce(
    (acc, product) => acc + product.price,
    0
  );
  const total = Math.round(totalReducer * 100) / 100;
  setTotalPrice(total);
}, [state.cart]);
Location: src/views/CartPage/CartPage.jsx:44-51

Payment Integration

EducaStream integrates with Stripe for secure payment processing.

Stripe Checkout Session

The PayButton component initiates the Stripe checkout flow:
const PayButton = ({ text, disabled }) => {
  const user = JSON.parse(localStorage.getItem("userOnSession"));
  const cartItems = JSON.parse(localStorage.getItem("cart"));
  
  const handleCheckout = () => {
    axios
      .post(`/payment/create-checkout-session`, {
        cartItems,
        userId: user.id,
      })
      .then((response) => {
        if (response.data.url) {
          localStorage.setItem("payment", JSON.stringify(response.data));
          window.location.href = response.data.url;
        }
      })
      .catch((err) => console.log(err.message));
  };

  return (
    <button
      className={style.finalizePurchase}
      onClick={() => handleCheckout()}
      disabled={disabled}
    >
      {text}
    </button>
  );
};
Location: src/Components/PayButton/Paybutton.jsx:4-33

Checkout API Request

cartItems
array
required
Array of course objects from localStorage containing course details
userId
string
required
ID of the authenticated user making the purchase

Payment Response

url
string
Stripe checkout session URL where user is redirected to complete payment
payment
string
Payment session ID stored in localStorage for verification

Checkout Confirmation

After successful payment, users are redirected to the checkout confirmation page.
1

Payment Verification

System retrieves payment data from localStorage and validates the transaction.
2

Course Enrollment

Calls postPaymentCart() to enroll user in purchased courses and associate payment record.
3

User Session Update

Fetches updated user data including new course enrollments and refreshes localStorage.
4

Cart Clearing

Dispatches CLEAR action to empty the cart and refresh the UI.
5

Confirmation Display

Shows success message with email confirmation notice and navigation to courses.

Enrollment Process

useEffect(() => {
  const session = JSON.parse(localStorage.getItem("userOnSession"));
  const cart = JSON.parse(localStorage.getItem("cart"));
  
  if (session && session.id && Array.isArray(cart)) {
    if (payment.payment) {
      enrollCourses(cart, session.id, session.email, payment.payment)
        .then(() => {
          return getUser(session.email);
        })
        .then((newUser) => {
          localStorage.setItem("userOnSession", JSON.stringify(newUser));
          updateContextUser(newUser);
          dispatch({ type: "CLEAR", payload: [] });
        })
        .catch((error) => new Error(error));
    }
  }
}, []);
Location: src/Components/Cart/CheckOut.jsx:24-46

Persistence

The cart automatically persists to localStorage on every change:
useEffect(() => {
  localStorage.setItem("cart", JSON.stringify(state.cart));
}, [state.cart]);
Location: src/context/CartContext.jsx:97-99 This ensures cart contents survive page refreshes and browser sessions.

Usage Hook

Components can access the cart using the useCart hook:
export const useCart = () => {
  const context = useContext(CartContext);

  if (!context) {
    throw new Error("useCart debe utilizarse dentro de un CartProvider");
  }

  return context;
};
Location: src/context/CartContext.jsx:108-116 Example usage:
const { state, dispatch } = useCart();

// Add to cart
dispatch({ type: "ADD_TO_CART", payload: courseObject });

// Remove from cart
dispatch({ type: "REMOVE_FROM_CART", payload: courseId });

// Access cart items
const cartItems = state.cart;

Build docs developers (and LLMs) love