Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
- Fixing minicart scrolling/styles
- Added useReducer
- Fixed index.html path to icons/manifest
- Fixed path on vite config to support without final slash
  • Loading branch information
Felipe Spengler committed Mar 21, 2024
1 parent 9bd4350 commit dfbb5ad
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 33 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The course is not updated to use latest technologies, so I challenged myself to
## Features (as part of the course)
- Login / Registration using Firebase auth with Google and Email/Password options
- Using useContext for Authentication, Products list and Minicart show/hide/render.
- Context changes later on the course: converted useStates inside the Context to useReducer
- Shop / Category pages with functional Add To Cart button + Minicart + Bag icon counter
- Cart/Minicart quantity changes + product removal + counter update
- Categories data comes from Firebase.
Expand Down
8 changes: 4 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="apple-touch-icon" sizes="180x180" href="/public/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/public/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/public/favicon-16x16.png">
<link rel="manifest" href="/public/site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<title>Clothing Store v1 | A React E-commerce Project</title>
</head>
<body>
Expand Down
8 changes: 4 additions & 4 deletions src/components/cart/cart-icon.component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import bagIcon from '../../assets/shopping-bag.svg'
import './cart-icon.styles.scss';

const CartIcon = () => {
const { openMinicart, setOpenMinicart, bagCount } = useContext(MinicartContext);
const { openMinicart, toggleMinicart, bagCount } = useContext(MinicartContext);

const toggleMinicart = () => {
setOpenMinicart(!openMinicart);
const handleMinicartToggle = () => {
toggleMinicart(!openMinicart);
}

return (
<button type='button' className='cart-icon-container' onClick={toggleMinicart}>
<button type='button' className='cart-icon-container' onClick={handleMinicartToggle}>
<img className='shopping-icon' src={bagIcon} alt='' />
<span className='item-count'>{bagCount}</span>
</button>
Expand Down
16 changes: 12 additions & 4 deletions src/components/cart/minicart.component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@ import CartItem from './cart-item.component.jsx';
import './minicart.styles.scss';

const Minicart = () => {
const { openMinicart, setOpenMinicart, cartItems, bagTotalPrice } = useContext(MinicartContext);
const { openMinicart, toggleMinicart, cartItems, bagTotalPrice } = useContext(MinicartContext);
const location = useLocation();

useEffect(() => {
if (openMinicart) {
setOpenMinicart(false);
toggleMinicart(false);
}
}, [location]); //eslint-disable-line react-hooks/exhaustive-deps

const closeMinicart = () => {
toggleMinicart(false)
}

return (
<div className={`minicart-container ${openMinicart && location.pathname !== '/checkout' ? 'to-show' : 'to-hide'}`}>
<div
className={
`minicart-container
${(openMinicart && location.pathname !== '/checkout')
? 'to-show' : 'to-hide'}`}>
<div className='minicart-content'>
<button type='button' className='close-minicart' onClick={() => setOpenMinicart(false)}>&#10005;</button>
<button type='button' className='close-minicart' onClick={closeMinicart}>&#10005;</button>
{
!cartItems.length ? (
<h2 className='empty-message'>
Expand Down
7 changes: 4 additions & 3 deletions src/components/cart/minicart.styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
position: absolute;
width: 280px;
height: auto;
max-height: 80vh;
display: flex;
flex-direction: column;
padding: 20px;
border: 1px solid black;
background-color: white;
Expand All @@ -24,6 +21,10 @@

.minicart-content {
position: relative;
display: flex;
max-height: 80vh;
flex-direction: column;
max-height: calc(100vh - 120px);
}

.empty-message {
Expand Down
84 changes: 69 additions & 15 deletions src/contexts/minicart.context.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useState } from 'react';
import { createContext, useState, useReducer } from 'react';

const handleItemAdd = (cartItems, productToAdd) => {
// Better approach, the one suggested by the course (commented below) transverse the same array twice for some reason, bad practice.
Expand Down Expand Up @@ -47,37 +47,91 @@ const handleRemoveOrDecreaseItem = (cartItems, productToModify, directRemove = f

export const MinicartContext = createContext({
openMinicart: false,
setOpenMinicart: () => { },
toggleMinicart: () => { },
cartItems: [],
addItemToCart: () => { },
removeOrDecreaseItem: () => {},
bagCount: 0,
bagTotalPrice: 0
});

const INITIAL_STATE = {
openMinicart: false,
cartItems: [],
bagCount: 0,
bagTotalPrice: 0
}

const MINICART_ACTION_TYPES = {
SET_CAR_ITEMS: 'SET_CAR_ITEMS',
TOGGLE_MINICART: 'TOGGLE_MINICART'
}

const minicartReducer = (state, action) => {
const { type, payload } = action;

switch (type) {
case MINICART_ACTION_TYPES.SET_CAR_ITEMS:
return {
...state,
...payload
}
case MINICART_ACTION_TYPES.TOGGLE_MINICART:
return {
...state,
openMinicart: payload
}
default:
throw new Error(`Unhandled type ${type} in userReducer`);
}
}

export const MinicartProvider = ({ children }) => {
const [openMinicart, setOpenMinicart] = useState(false);
const [cartItems, setCartItems] = useState([]);
const bagCount = cartItems.length
? cartItems.reduce((totalQty, cartItem) => totalQty + cartItem.quantity, 0)
: 0;
const bagTotalPrice = cartItems.length
? cartItems.reduce((totalPrice, cartItem) => totalPrice + cartItem.price * cartItem.quantity, 0)
: 0;
const [{ openMinicart, cartItems, bagCount, bagTotalPrice }, dispatch] = useReducer(minicartReducer, INITIAL_STATE);

const updateMinicartReducer = (newCartItems) => {
const newBagCount = newCartItems.length
? newCartItems.reduce((totalQty, cartItem) => totalQty + cartItem.quantity, 0)
: 0;
const newBagTotalPrice = newCartItems.length
? newCartItems.reduce((totalPrice, cartItem) => totalPrice + cartItem.price * cartItem.quantity, 0)
: 0;

dispatch({
type: MINICART_ACTION_TYPES.SET_CAR_ITEMS,
payload: {
cartItems: newCartItems,
bagCount: newBagCount,
bagTotalPrice: newBagTotalPrice
}
});
};

const toggleMinicart = (value) => {
dispatch({
type: MINICART_ACTION_TYPES.TOGGLE_MINICART,
payload: value
});
}

const addItemToCart = (productToAdd) => {
setCartItems(handleItemAdd(cartItems, productToAdd));
setOpenMinicart(true);
const newCartItems = handleItemAdd(cartItems, productToAdd);
updateMinicartReducer(newCartItems);
toggleMinicart(true);
}

const removeOrDecreaseItem = (...params) => {
setCartItems(handleRemoveOrDecreaseItem(cartItems, ...params));
const removeOrDecreaseItem = (...params) => {
const newCartItems = handleRemoveOrDecreaseItem(cartItems, ...params);
updateMinicartReducer(newCartItems);
if (!newCartItems.length && openMinicart) {
toggleMinicart(false);
}
}

const value =
{
openMinicart,
setOpenMinicart,
toggleMinicart,
cartItems,
addItemToCart,
removeOrDecreaseItem,
Expand Down
32 changes: 30 additions & 2 deletions src/contexts/user.context.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useEffect, useState } from 'react';
import { createContext, useEffect, useReducer } from 'react';
import { authChangedListener, createUserDocFromAuth } from '../utils/firebase/firebase.utils';

// The actual value that will be accessed
Expand All @@ -7,9 +7,37 @@ export const UserContext = createContext({
setCurrentUser: () => null
});

const USER_ACTION_TYPES = {
SET_CURRENT_USER: 'SET_CURRENT_USER'
}

const userReducer = (state, action) => {
const { type, payload } = action;

switch (type) {
case USER_ACTION_TYPES.SET_CURRENT_USER:
return {
...state,
currentUser: payload
}
default:
throw new Error(`Unhandled type ${type} in userReducer`);
}
}

const INITIAL_STATE = {
currentUser: null
}

// The actual component
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [state, dispatch] = useReducer(userReducer, INITIAL_STATE);
const { currentUser } = state;

const setCurrentUser = (user) => {
dispatch({ type: USER_ACTION_TYPES.SET_CURRENT_USER, payload: user });
};

const value = { currentUser, setCurrentUser };

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: '/clothing-store/'
base: '/clothing-store'
})

0 comments on commit dfbb5ad

Please sign in to comment.