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:
Adds a course to the cart with duplicate checking and purchase validation
Removes a specific course from the cart by ID
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
Product List
Displays all cart items with images, names, and remove buttons. If cart is empty, shows a helpful message with link to courses.
Purchase Summary
Shows itemized list of courses and prices with calculated total.
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
Array of course objects from localStorage containing course details
ID of the authenticated user making the purchase
Payment Response
Stripe checkout session URL where user is redirected to complete payment
Payment session ID stored in localStorage for verification
Checkout Confirmation
After successful payment, users are redirected to the checkout confirmation page.
Payment Verification
System retrieves payment data from localStorage and validates the transaction.
Course Enrollment
Calls postPaymentCart() to enroll user in purchased courses and associate payment record.
User Session Update
Fetches updated user data including new course enrollments and refreshes localStorage.
Cart Clearing
Dispatches CLEAR action to empty the cart and refresh the UI.
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;