Skip to content

Commit

Permalink
Feature complete
Browse files Browse the repository at this point in the history
- Several style adjustments
- Added loading for category fetch
- Changed site paths to hash to avoid issues with GitHub Pages
- Fixed Minicart appearing for a millisecond on page load
- Added redux-persist back with redux-toolkit
- Fixed product removal from chheckout
- Fixed Login/Logout nav link swap on site load
- Checkout scroll position when opening should now reset to top
  • Loading branch information
Felipe Spengler committed Mar 28, 2024
1 parent 9ecc405 commit 1447542
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 136 deletions.
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# (WIP) Clothing Store v1 | A React E-commerce custom project

Reac basic e-commerce store, build as part of the course **"Complete React Developer"** from _"Zero To Master"_. (PS: read the *About the Course* section down below)
React basic e-commerce store, build as part of the course **"Complete React Developer"** from _"Zero To Master"_. (PS: read the *About the Course* section down below)

**Work In Progress**

It's a long/extensive course and there are many modules to go, the current version is just a milestone on the course timeline. I also want to implement many improvements (the course base code is not even responsive and has some bad practices)
My React experience has been on and off as my career led to be a Senior Frontend Developer on other platforms (Salesforce SFCC) that don't use React, so I decided to get an extensive course to challenge improve and expand my experience with it.

The course is not updated to use latest technologies, so I challenged myself to replicate what the course explained using more up to date approaches.
**Work In Progress**

» Open the *current version here: https://fleps.github.io/clothing-store
» Open the *current version here: https://fleps.github.io/clothing-store/

*Published version is alwasy based on the code from the [release branch](https://github.com/fleps/clothing-store/tree/releases).
*Published version is always based on the code from the [release branch](https://github.com/fleps/clothing-store/tree/releases).

## Project Technical Details

Expand All @@ -25,34 +23,36 @@ The course is not updated to use latest technologies, so I challenged myself to
- Login / Registration using Firebase auth with Google and Email/Password options
- Initially using `useContext` for Authentication, Products list and Minicart show/hide/render.
- Refactor #1: Context changes, converting `useStates` inside the Context to `useReducer`
- Refactor #2: Contexts gets replaced by using Redux. `reselect` added to the project to memoize the category array and avoid recreating it without need and unnecessart re-renders. Redux-thunk and Redux-Saga examples.
- Refactor #3: Manual Redux reworked to use Redux-Toolkit
- Refactor #2: Contexts gets replaced by using Redux. `reselect` added to the project to memoize the category array and avoid recreating it without need and unnecessary re-renders. **Redux-thunk** and **Redux-Saga** examples.
- Refactor #3: Manual Redux reworked to use **Redux-Toolkit**
- 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.

## Extra features added by me so far (not part of the course)
## Extra features / learning by me (not part of the course)
- Products have currency and proper price format.
- Minicart open / close improvements:
- Minicart auto opens when adding a product to the bag (if the user is not on Cart page). Very basic for e-commerce.
- Minicart closes when changing "pages".
- Minicart has CSS animation to open/close, doesn't just appear/disappear.
- Logic improved: reworked calls to avoid unnecessary re-renders when opening / closing.

- Unnecessary components grouped into one (the coure is totally overboard)
- Unnecessary `useState` + `useEffects` removed following React docs.
- Added cleanup function on `useEffect` tha fetches the category data from Firebase (course never mentions this)
- Added cleanup function on `useEffect` that fetches the category data from Firebase (course never mentions this)
- Semantic / accessible HTML (course code has divs nested into spans, divs/spans having click events...)
- Optimized arrays methods, course teaches bad practices doing 2 (or even more) transverse on the same arrays for no reason.
- Github Actions Workflow implemented to auto-publish when a new version gets pushed to /releases branch
- Navigation fix position with glass effect.
- Navigation style to be position fixed and with glass effect.
- Added **Redux-Persist** for minicart with **Redux-Toolkit**.
- Added Loading element on categories async fecth using **Redux-Toolkit** `createAsyncThunk` approach.
- Added scroll position reset when navigating to checkout

## About the Course
While I'm still doing the course and plan to finish all modules, I'm not sure I recommend it. I can't judge the React part itself too much, and the course still has its values to introduce many different topics needed for someone wanting to learn React, but I expected way more from it.

On the pure Front-End side of things (proper UX for e-commerce sites, good practices for HTML/JS/CSS, responsive layout) the course **lacks tremendously** and **actually teaches bad practices** (double transversing arrays, nesting divs inside spans, attaching onClick events do spans/divs...).
On the pure Front-End side of things (proper UX for e-commerce sites, good practices for HTML/JS/CSS, responsive layout) the course **lacks tremendously** and **actually teaches bad practices** (double array transverse, nesting divs inside spans, attaching onClick events do spans/divs...).

Also, the teaching method can be really infuriating as it's based on rafacoring over and over. Basically you will build some functionality to learn something (like useEffect), but even as new to React and had the impression that something was off, the approach had flaws and sub-optimal code. Then, at a later lesson, it REFACTORS the entire functionality to remove that code (prob that's why it had flaws, he knew it was going to be removed), to teach another approach. And AGAIN, there are flaws on the new approach, as later on the course you will need to refactor AGAIN. So far this happened with Auth, Category and Minicart functionalities, having refactored them 3 times already and each one had some caveats and weird flaws that many stundents point out in the video comments. And at the end, do you really LEARN something if you are always see
Also, the teaching method can be frustrating as it's based on refactoring over and over. Basically you will build some functionality to learn something (like useEffect), but even as new to React and had the impression that something was off, the approach had flaws and sub-optimal code. Then, at a later lesson, it REFACTORS the entire functionality to remove that code (prob that's why it had flaws, he knew it was going to be removed), to teach another approach. And AGAIN, there are flaws on the new approach, as later on the course you will need to refactor AGAIN. So far this happened with Auth, Category and Minicart functionalities, having refactored them 3 times already and each one had some caveats and weird flaws that many students point out in the video comments. And at the end, do you really LEARN something if you are always see

## What's Next
My idea is to keep working on this to finish all modules and at the end build a "v2" that resembles more an e-commerce site, with better (AND RESPONSIVE) UI, more features and refactoring some things the course teached on a way that doesn't seems the best approach according to my readings.
My idea is to keep working on this to finish all modules and at the end build a "v2" that resembles more an e-commerce site, with better (AND RESPONSIVE) UI, more features and refactoring some things the course taught on a way that doesn't seems the best approach according to my readings.
8 changes: 4 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const App = () => {
if (user) {
createUserDocFromAuth(user);
}
const pickedUser = user && (({ accessToken, email }) => ({ accessToken, email }))(user);
const pickedUser = user ? (({ accessToken, email }) => ({ accessToken, email }))(user) : 'logged-out';
dispatch(setCurrentUser(pickedUser));
})
return unsubscribe;
Expand All @@ -30,9 +30,9 @@ const App = () => {
<Routes>
<Route path='/' element={<Navigation />}>
<Route index element={<Home />} />
<Route path='/shop/*' element={<Shop />} />
<Route path='/login' element={<Login />} />
<Route path='/checkout' element={<CheckoutComponent />} />
<Route path='shop/*' element={<Shop />} />
<Route path='login' element={<Login />} />
<Route path='checkout' element={<CheckoutComponent />} />
</Route>
</Routes>
)
Expand Down
8 changes: 4 additions & 4 deletions src/components/cart/minicart.component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const Minicart = () => {

return (
<div
className={
`minicart-container
${(openMinicart && location.pathname !== '/checkout')
? 'to-show' : 'to-hide'}`}>
className={`minicart-container ${
(openMinicart && location.pathname !== '/checkout') && 'to-show'
}`}
>
<div className='minicart-content'>
<button type='button' className='close-minicart' onClick={closeMinicart}>&#10005;</button>
{
Expand Down
35 changes: 7 additions & 28 deletions src/components/cart/minicart.styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
right: 22px;
z-index: 5;
box-shadow: 5px 5px 10px #00000070;
opacity: 0;
visibility: hidden;
transition: opacity 150ms ease-in, visibility 0s;
transition-delay: 0ms, 150ms;

&.to-show {
animation: inAnimation 150ms ease-in
}

&.to-hide {
animation: outAnimation 100ms ease-out;
animation-fill-mode: forwards
opacity: 1;
visibility: visible;
transition-delay: 0ms, 0ms;
}

.minicart-content {
Expand Down Expand Up @@ -61,25 +62,3 @@
justify-content: center;
}
}

@keyframes inAnimation {
0% {
opacity: 0;
visibility: hidden;
}
100% {
opacity: 1;
visibility: visible;
}
}

@keyframes outAnimation {
0% {
opacity: 1;
}
100% {
opacity: 0;
visibility: hidden;
}
}

1 change: 0 additions & 1 deletion src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ html, body {
font-family: "Barlow Condensed", sans-serif;
font-weight: 300;
font-style: normal;
height: 100%;
margin: 0;
}

Expand Down
13 changes: 9 additions & 4 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from "react-router-dom";
import { HashRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

import { store, persistor } from './store/store.js';

import { store } from './store/store.js';
import App from './App.jsx'

import './index.scss'


ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter basename='/clothing-store'>
<PersistGate loading={null} persistor={persistor}>
<HashRouter basename='/'>
<App />
</BrowserRouter>
</HashRouter>
</PersistGate>
</Provider>
</React.StrictMode>,
)
46 changes: 26 additions & 20 deletions src/routes/category-list-page/category-list-page.component.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';

import { selectCategoriesMap } from '../../store/category.selector';
import { selectCategoriesMap, selectCategoriesIsLoading } from '../../store/category.selector';
import ProductCard from '../../components/product-card/product-card.component';
import SpinnerComponent from '../../components/spinner/spinner.component';


const CategoryListPage = () => {
const categoriesMap = useSelector(selectCategoriesMap);
const isLoading = useSelector(selectCategoriesIsLoading);

return (
<div className='container page-container'>
{
Object.keys(categoriesMap).map(title => {
return (
<div className='cat-wrapper' key={title}>
<h2 className='cat-name'>
{title}
<Link className='see-link' to={title}>
See all {categoriesMap[title].length} products »
</Link>
</h2>
<div className='shop-container'>
{categoriesMap[title].slice(0, 4).map((product) => {
return (
<ProductCard key={product.id} product={product} />
)
})}
{isLoading ? (
<SpinnerComponent />
) : (
Object.keys(categoriesMap).map(title => {
return (
<div className='cat-wrapper' key={title}>
<h2 className='cat-name'>
{title}
<Link className='see-link' to={title}>
See all {categoriesMap[title].length} products »
</Link>
</h2>
<div className='shop-container'>
{categoriesMap[title].slice(0, 4).map((product) => {
return (
<ProductCard key={product.id} product={product} />
)
})}
</div>
</div>
</div>
)
})
)
})
)
}
</div>
);
Expand Down
38 changes: 24 additions & 14 deletions src/routes/category/category.component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';

import { selectCategoriesMap } from '../../store/category.selector';
import { selectCategoriesMap, selectCategoriesIsLoading } from '../../store/category.selector';
import ProductCard from '../../components/product-card/product-card.component';
import SpinnerComponent from '../../components/spinner/spinner.component';

const Category = () => {
const { category } = useParams();
const categoriesMap = useSelector(selectCategoriesMap);
const isLoading = useSelector(selectCategoriesIsLoading);
const [products, setProducts] = useState(categoriesMap[category]);

useEffect(() => {
Expand All @@ -16,19 +18,27 @@ const Category = () => {

return (
<div className='container page-container'>
<h2 className='cat-page-name'>
{category}
<span className='product-count'>
{products && ` ${products.length} products`}
</span>
</h2>
<div className='shop-container'>
{products && products.map((product) => {
return (
<ProductCard key={product.id} product={product} />
)
})}
</div>
{
isLoading ? (
<SpinnerComponent />
) : (
<>
<h2 className='cat-page-name'>
{category}
<span className='product-count'>
{products && ` ${products.length} products`}
</span>
</h2>
<div className='shop-container'>
{products && products.map((product) => {
return (
<ProductCard key={product.id} product={product} />
)
})}
</div>
</>
)
}
</div>
);
}
Expand Down
26 changes: 18 additions & 8 deletions src/routes/checkout/checkout.component.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { useLayoutEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom';

import { addItemToCart, removeOrDecreaseItem } from '../../store/minicart.reducer';
import { selectBagTotalPrice, selectCartItems } from '../../store/minicart.selector';

import './checkout.styles.scss';
import { Link } from 'react-router-dom';

const CheckoutComponent = () => {
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
const bagTotalPrice = useSelector(selectBagTotalPrice);
const location = useLocation();

const handleAddToCart = (product) => dispatch(addItemToCart(product));
const handleRemoveOrDecrease = (product, toRemove) => dispatch(removeOrDecreaseItem(product, toRemove));
const handleRemoveOrDecrease = (product, directRemove = false) => dispatch(removeOrDecreaseItem({ product, directRemove }));

useLayoutEffect(() => {
window.scrollTo({ top:0, left:0, behavior: "instant" });
}, [location.pathname]);

return (
<div className='container checkout-container'>
<div className={`container checkout-container ${!cartItems.length && 'empty-container'}`}>
<h1>Checkout</h1>
{
cartItems.length > 0 && (
<div className='total'>
<span>Total:</span> $ {bagTotalPrice.toFixed(2)}
</div>
)
}
{
!cartItems.length ? (
<>
<div className='empty-checkout'>
<h2>Your Checkout is empty</h2>
<Link className='button-container' to={'/shop'}>Go Shopping</Link>
</>
</div>
) : (
<>
<div className='checkout-header semibold-barlow-cond'>
Expand Down Expand Up @@ -66,9 +79,6 @@ const CheckoutComponent = () => {
)
})
}
<div className='total'>
Total: $ {bagTotalPrice.toFixed(2)}
</div>
</>
)
}
Expand Down
Loading

0 comments on commit 1447542

Please sign in to comment.