├── src
├── pages
│ ├── NotFound.jsx
│ ├── Home.jsx
│ ├── Products.jsx
│ └── Cart.jsx
├── utilites
│ └── currencyFormatter.js
├── components
│ ├── Footer.jsx
│ ├── Slide.jsx
│ ├── Navbar.jsx
│ ├── Cart.jsx
│ └── Slider.jsx
├── app
│ └── store.js
├── index.js
├── App.js
├── features
│ └── products
│ │ ├── productSlice.js
│ │ └── cartSlice.js
└── index.css
├── tailwind.config.js
├── public
└── index.html
├── .gitignore
├── package.json
└── README.md
/src/pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | const NotFound = () => {
2 | return
NotFound
;
3 | };
4 |
5 | export default NotFound;
6 |
--------------------------------------------------------------------------------
/src/utilites/currencyFormatter.js:
--------------------------------------------------------------------------------
1 | export const currencyFormatter = (price) => {
2 | if (!price) return;
3 | return price.toLocaleString("en-US", { style: "currency", currency: "USD" });
4 | };
5 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import Slider from "../components/Slider";
2 | import Products from "./Products";
3 |
4 | const Home = () => {
5 | return (
6 |
10 | );
11 | };
12 |
13 | export default Home;
14 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
4 |
5 | © {new Date().getFullYear()} Tech Alpha. All rights reserved
6 |
7 |
8 | );
9 | };
10 |
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tech Alpha
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 |
3 | import productsReducer, {
4 | productFetching,
5 | } from "../features/products/productSlice";
6 |
7 | import cartReducer from "../features/products/cartSlice";
8 |
9 | export const store = configureStore({
10 | reducer: {
11 | products: productsReducer,
12 | cart: cartReducer,
13 | },
14 | });
15 |
16 | store.dispatch(productFetching());
17 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { BrowserRouter } from "react-router-dom";
4 | import App from "./App";
5 | import "./index.css";
6 |
7 | import { store } from "./app/store";
8 | import { Provider } from "react-redux";
9 |
10 | const root = ReactDOM.createRoot(document.getElementById("root"));
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/src/pages/Products.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import Cart from "../components/Cart";
3 |
4 | const Products = () => {
5 | const { items: data, status } = useSelector((state) => state.products);
6 | console.log("data: ", data);
7 | return (
8 |
9 |
10 | browse all products
11 |
12 |
13 | {status &&
{status}
}
14 |
15 | {data.map((product) => (
16 |
17 | ))}
18 |
19 |
20 | );
21 | };
22 |
23 | export default Products;
24 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 |
3 | import Navbar from "./components/Navbar";
4 | import Footer from "./components/Footer";
5 | import Home from "./pages/Home";
6 | import Products from "./pages/Products";
7 | import Cart from "./pages/Cart";
8 | import NotFound from "./pages/NotFound";
9 | import { ToastContainer } from "react-toastify";
10 | import "react-toastify/dist/ReactToastify.css";
11 |
12 | const App = () => {
13 | return (
14 | <>
15 |
16 |
17 |
18 |
19 | } />
20 | } />
21 | } />
22 | } />
23 | } />
24 |
25 |
26 |
27 | >
28 | );
29 | };
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/src/components/Slide.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | const Slide = ({ image }) => {
5 | return (
6 |
7 |
8 |
9 | {image.headline}
10 |
11 |
12 | {image.detail}
13 |
14 |
18 |
19 | {image.cta}
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Slide;
28 |
--------------------------------------------------------------------------------
/src/features/products/productSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import axios from "axios";
3 |
4 | const initialState = {
5 | items: [],
6 | status: null,
7 | };
8 |
9 | // "https://eager-sable-airedale.glitch.me/products"
10 | export const productFetching = createAsyncThunk(
11 | "products/productFetching",
12 | async () => {
13 | const res = await axios.get(
14 | "https://tech-alpha-jj7a.onrender.com/api/products"
15 | );
16 | return res.data;
17 | }
18 | );
19 |
20 | export const productsSlice = createSlice({
21 | name: "products",
22 | initialState,
23 | reducers: {},
24 | extraReducers: (builder) => {
25 | builder.addCase(productFetching.pending, (state, action) => {
26 | state.status = "loading...";
27 | });
28 |
29 | builder.addCase(productFetching.fulfilled, (state, action) => {
30 | state.status = "";
31 | state.items = action.payload;
32 | });
33 |
34 | builder.addCase(productFetching.rejected, (state, action) => {
35 | state.status = "something went wrong!";
36 | state.items = action.payload; //rtk uses a packeg called "immer"
37 | });
38 | },
39 | });
40 |
41 | export default productsSlice.reducer;
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tect-alpha-a3",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.9.1",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.4.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "axios": "^1.2.2",
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0",
13 | "react-icons": "^4.7.1",
14 | "react-redux": "^8.0.5",
15 | "react-router-dom": "^6.6.1",
16 | "react-scripts": "5.0.1",
17 | "react-toastify": "^9.1.1",
18 | "web-vitals": "^2.1.4"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "devDependencies": {
45 | "tailwindcss": "^3.2.4"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { BsCart3 } from "react-icons/bs";
3 | import { useSelector } from "react-redux";
4 |
5 | const Navbar = () => {
6 | const { cartItems } = useSelector((state) => state.cart);
7 | return (
8 |
9 |
10 |
11 |
12 | techAlpha
13 |
14 |
15 |
16 |
17 | Home
18 |
19 |
20 | Products
21 |
22 |
23 |
24 |
25 |
26 | {cartItems.length}
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Navbar;
37 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap");
6 | @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
7 |
8 | html,
9 | body {
10 | overflow-x: hidden;
11 | }
12 |
13 | body {
14 | font-family: "Poppins", sans-serif;
15 | }
16 |
17 | /* Utilities */
18 | .space-font {
19 | font-family: "Space Grotesk", sans-serif;
20 | }
21 |
22 | /******** Navbar Desing Starts ********/
23 | .nav-link {
24 | position: relative;
25 | }
26 |
27 | .nav-link::after {
28 | content: "";
29 | width: 0%;
30 | height: 1px;
31 | background-color: #f9fafb;
32 | position: absolute;
33 | bottom: 0;
34 | left: 50%;
35 | transform: translateX(-50%);
36 | transition: all 0.3s;
37 | }
38 |
39 | .nav-link:hover::after {
40 | width: 100%;
41 | }
42 |
43 | /********** Slider Design Starts */
44 | .slider {
45 | height: calc(100vh - 5rem);
46 | width: 500vw;
47 | display: flex;
48 | transition: 1s ease;
49 | }
50 |
51 | .slide {
52 | width: 100vw;
53 | height: 100%;
54 | background-position: center;
55 | background-repeat: no-repeat;
56 | background-size: cover;
57 | }
58 |
59 | /********** Slide Design Starts */
60 | .slide-btn {
61 | position: relative;
62 | }
63 |
64 | .slide-btn::before {
65 | content: "";
66 | position: absolute;
67 | top: 0;
68 | right: 0;
69 | bottom: 0;
70 | left: 0;
71 | z-index: 1;
72 | background-color: #f97316;
73 | width: 0%;
74 | transition: 0.5s ease;
75 | }
76 |
77 | .slide-btn:hover::before {
78 | width: 100%;
79 | }
80 |
--------------------------------------------------------------------------------
/src/components/Cart.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { useNavigate } from "react-router-dom";
3 | import { addToCart } from "../features/products/cartSlice";
4 | import { currencyFormatter } from "../utilites/currencyFormatter";
5 |
6 | const Cart = ({ product }) => {
7 | const navigate = useNavigate();
8 | const dispatch = useDispatch();
9 |
10 | const addToCartHandler = (product) => {
11 | dispatch(addToCart(product));
12 |
13 | navigate("/cart");
14 | };
15 |
16 | return (
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 | {product.category}
29 |
30 |
31 | {product.name}
32 |
33 |
34 | {product.description}
35 |
36 |
37 |
38 | {currencyFormatter(product.price)}
39 |
40 | addToCartHandler(product)}
42 | className="uppercase font-medium bg-violet-500 text-violet-50 py-2 px-4 rounded-md hover:text-orange-50 hover:bg-orange-500 duration-500"
43 | >
44 | Add to cart
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default Cart;
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Tech Alpha
3 |
4 | Welcome to Tech-Alpha, A MERN stack e-commerce platform that redefines the online shopping experience. Seamlessly merging the power of MongoDB, Express.js, React, and Node.js, Tech-Alpha offers a dynamic and feature-rich vironment for customers.
5 |
6 | ## Features
7 |
8 | - This website is built with React, Redux, TailwindCSS, RTK Query and Redux Toolkit.
9 | - It offers a dynamic shopping experience with a modern UI design.
10 | - Perfect example of how to create a fully-functional e-commerce platform with these technologies.
11 |
12 | ## Tools and Technologies
13 |
14 | - **Frontend:**
15 | - React for building the user interface.
16 | - Redux for state management.
17 | - Axios for making API requests.
18 | - Tailwind Css for styling.
19 |
20 | - **Backend:**
21 | - Node.js and Express.js for the server.
22 | - MongoDB for the database.
23 | - Mongoose for object modeling.
24 |
25 | ## Installation (Client)
26 |
27 | 1. Clone the repository: `git clone https://github.com/Shm-Rsuf/tech-alpha-client.git`
28 | 2. Navigate to the client directory: `cd tech-alpha-client`
29 | 3. Install dependencies: `npm install`
30 | 4. Start the client: `npm start`
31 | 5. Access the client in your browser at: `https://localhost:3000`
32 |
33 | ## Installation (Server)
34 |
35 | 1. Clone the repository: `git clone https://github.com/Shm-Rsuf/tech-alpha-server.git`
36 | 2. Navigate to the server directory: `cd tech-alpha-server`
37 | 3. Install dependencies: `npm install`
38 | 4. Start the server: `npm start`
39 | 5. Create a `.env` file in the server directory and set the necessary environment variables (e.g, MongoDB URI, PORT).
40 | -`MONGODB_URI`: the MongoDB connection string
41 | - `PORT`: a secret string for JWT authentication
42 | 6. The server will be running at: `http://localhost:8000`
43 |
44 | ## Links
45 |
46 | - [Live-Link](https://tech-alpha-a3.netlify.app)
47 | - [Front-End-Link](https://github.com/Shm-Rsuf/tech-alpha-client.git)
48 | - [Back-End-Link](https://github.com/Shm-Rsuf/tech-alpha-server.git)
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/components/Slider.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
3 | import Slide from "./Slide";
4 |
5 | const data = [
6 | {
7 | id: 1,
8 | src: "https://i.ibb.co/XszmG02/camera.jpg",
9 | headline: "DSLR cameras for stunning photos",
10 | detail:
11 | "Are you an aspiring photographer looking to take your skills to the next level? Our DSLR cameras offer advanced features and high-quality image sensors to help you capture stunning photos. From landscape shots to portraits, these cameras are perfect for capturing all types of subjects.",
12 | cta: "Shop DSLR cameras now",
13 | category: "cameras",
14 | },
15 | {
16 | id: 2,
17 | src: "https://i.ibb.co/mtc8v16/tv.jpg",
18 | headline: "Upgrade your home entertainment with our TVs",
19 | detail:
20 | "Experience the latest in home entertainment with our selection of TVs. From sleek and modern designs to advanced features like 4K resolution and smart capabilities, our TVs will bring your favorite movies, TV shows, and streaming content to life.",
21 | cta: "Shop TVs and upgrade now",
22 | category: "tvs",
23 | },
24 | {
25 | id: 3,
26 | src: "https://i.ibb.co/kmr5qQv/headphones.jpg",
27 | headline: "Enhance your listening experience",
28 | detail:
29 | "Take your music, movies, and more to the next level with our headphones. Our selection offers a range of styles and features, including noise-cancelling technology, wireless connectivity, and comfortable designs for all-day wear.",
30 | cta: "Experience enhanced sound",
31 | category: "headphones",
32 | },
33 | {
34 | id: 4,
35 | src: "https://i.ibb.co/JqxDhvZ/console.jpg",
36 | headline: "Take your gaming to the next level",
37 | detail:
38 | "Elevate your gaming experience with our selection of gaming consoles. From the latest models to classic systems, we have a console for every type of gamer. Our consoles offer advanced graphics, fast processing speeds, and a variety of exclusive games to choose from.",
39 | cta: "Shop consoles and play now",
40 | category: "consoles",
41 | },
42 | {
43 | id: 5,
44 | src: "https://i.ibb.co/YbS7mL2/smart-watches.jpg",
45 | headline: "Stay connected with smart watches",
46 | detail:
47 | "Stay connected and on top of your day with our smart watches. Our selection offers a range of styles and features, including fitness tracking, phone notifications, and voice assistants. These watches are the perfect combination of functionality and style.",
48 | cta: "Connect with a smart watch",
49 | category: "smart-watches",
50 | },
51 | ];
52 |
53 | const Slider = () => {
54 | const [currentSlide, setCurrentSlide] = useState(0);
55 |
56 | const prevSlide = () => {
57 | console.log("first");
58 | setCurrentSlide(
59 | currentSlide === 0 ? data.length - 1 : (prevSlide) => prevSlide - 1
60 | );
61 | };
62 | const nextSlide = () => {
63 | setCurrentSlide(
64 | currentSlide === data.length - 1 ? 0 : (prevSlide) => prevSlide + 1
65 | );
66 | };
67 |
68 | return (
69 |
70 |
74 | {data.map((image) => (
75 |
76 | ))}
77 |
78 |
79 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | );
98 | };
99 |
100 | export default Slider;
101 |
--------------------------------------------------------------------------------
/src/features/products/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { toast } from "react-toastify";
3 |
4 | const initialState = {
5 | cartItems: localStorage.getItem("cartItems")
6 | ? JSON.parse(localStorage.getItem("cartItems"))
7 | : [],
8 | cartTotalAmount: 0,
9 | };
10 |
11 | const cartSlice = createSlice({
12 | name: "cart",
13 | initialState,
14 | reducers: {
15 | //add to cart implementation is here...
16 | addToCart(state, action) {
17 | //if the item is already in the cart
18 | const existedItemIndex = state.cartItems.findIndex(
19 | (item) => item._id === action.payload._id
20 | );
21 |
22 | //if item exist
23 | if (existedItemIndex >= 0) {
24 | //increase quantity
25 | state.cartItems[existedItemIndex].cartQuantity += 1;
26 | //toast is here
27 | toast.info("Quantity increased", {
28 | position: "bottom-left",
29 | autoClose: 1500,
30 | hideProgressBar: false,
31 | closeOnClick: false,
32 | pauseOnHover: true,
33 | draggable: true,
34 | progress: undefined,
35 | theme: "dark",
36 | });
37 | } else {
38 | //add to cart
39 | const assembledItem = { ...action.payload, cartQuantity: 1 };
40 | state.cartItems.push(assembledItem);
41 | toast.success("Product added", {
42 | position: "bottom-left",
43 | autoClose: 1500,
44 | hideProgressBar: false,
45 | closeOnClick: false,
46 | pauseOnHover: true,
47 | draggable: true,
48 | progress: undefined,
49 | theme: "dark",
50 | });
51 | }
52 | //add to local storage
53 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
54 | },
55 |
56 | //card removed
57 | removeFromCart(state, action) {
58 | const updatedCaerItem = state.cartItems.filter(
59 | (item) => item._id !== action.payload._id
60 | );
61 | state.cartItems = updatedCaerItem;
62 | //toast is here
63 | toast.error("Item removed", {
64 | position: "bottom-left",
65 | autoClose: 1500,
66 | hideProgressBar: false,
67 | closeOnClick: false,
68 | pauseOnHover: true,
69 | draggable: true,
70 | progress: undefined,
71 | theme: "dark",
72 | });
73 |
74 | //updated local storage
75 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
76 | },
77 |
78 | //clear cart
79 | clearCart(state, action) {
80 | state.cartItems = [];
81 |
82 | //toast is here
83 | toast.error("Cart cleared", {
84 | position: "bottom-left",
85 | autoClose: 1500,
86 | hideProgressBar: false,
87 | closeOnClick: false,
88 | pauseOnHover: true,
89 | draggable: true,
90 | progress: undefined,
91 | theme: "dark",
92 | });
93 |
94 | //updated local storage
95 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
96 | },
97 |
98 | //decrease cart
99 | decreaseCart(state, action) {
100 | const itemIndex = state.cartItems.findIndex(
101 | (item) => item._id === action.payload._id
102 | );
103 |
104 | //if index is available
105 | if (state.cartItems[itemIndex].cartQuantity > 1) {
106 | state.cartItems[itemIndex].cartQuantity -= 1;
107 |
108 | //toast is here
109 | toast.info("Quantity decreased", {
110 | position: "bottom-left",
111 | autoClose: 1500,
112 | hideProgressBar: false,
113 | closeOnClick: false,
114 | pauseOnHover: true,
115 | draggable: true,
116 | progress: undefined,
117 | theme: "dark",
118 | });
119 | } else if (state.cartItems[itemIndex].cartQuantity === 1) {
120 | const updatedCartItems = state.cartItems.filter(
121 | (item) => item._id !== action.payload._id
122 | );
123 | state.cartItems = updatedCartItems;
124 |
125 | //toast is here
126 | toast.error("Item removed", {
127 | position: "bottom-left",
128 | autoClose: 1500,
129 | hideProgressBar: false,
130 | closeOnClick: false,
131 | pauseOnHover: true,
132 | draggable: true,
133 | progress: undefined,
134 | theme: "dark",
135 | });
136 | }
137 | //updated local storage
138 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
139 | },
140 |
141 | //total price calculation
142 | getSubtotal(state, action) {
143 | const subtotal = state.cartItems.reduce((acc, item) => {
144 | const { price, cartQuantity } = item;
145 | const itemTotal = price * cartQuantity;
146 | acc += itemTotal;
147 | return acc;
148 | }, 0);
149 |
150 | state.cartTotalAmount = subtotal;
151 | },
152 | },
153 | });
154 |
155 | export const {
156 | addToCart,
157 | removeFromCart,
158 | clearCart,
159 | decreaseCart,
160 | getSubtotal,
161 | } = cartSlice.actions;
162 | export default cartSlice.reducer;
163 |
--------------------------------------------------------------------------------
/src/pages/Cart.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { BsArrowLeft } from "react-icons/bs";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { Link } from "react-router-dom";
5 | import {
6 | addToCart,
7 | clearCart,
8 | decreaseCart,
9 | getSubtotal,
10 | removeFromCart,
11 | } from "../features/products/cartSlice";
12 | import { currencyFormatter } from "../utilites/currencyFormatter";
13 |
14 | const Cart = () => {
15 | const { cartItems: data, cartTotalAmount: subtotal } = useSelector(
16 | (state) => state.cart
17 | );
18 | const dispatch = useDispatch();
19 |
20 | const handleRemove = (product) => {
21 | dispatch(removeFromCart(product));
22 | };
23 |
24 | const decreaseHandler = (product) => {
25 | dispatch(decreaseCart(product));
26 | };
27 |
28 | const increaseHandler = (product) => {
29 | dispatch(addToCart(product));
30 | };
31 |
32 | useEffect(() => {
33 | dispatch(getSubtotal());
34 | }, [data, dispatch]);
35 |
36 | return (
37 |
38 |
39 | {data.length > 0
40 | ? `You've added ${data.length} item${data.length > 1 ? "s" : ""}`
41 | : "Your cart is empty"}
42 |
43 | {data.length === 0 && (
44 |
45 | shopping now
46 |
47 | )}
48 | {data.length > 0 && (
49 | <>
50 |
51 |
52 |
product
53 |
unit price
54 |
quantity
55 |
total price
56 |
57 |
58 | {data.map((product) => (
59 |
63 |
64 |
69 |
70 | {product.name}
71 | handleRemove(product)}
73 | className="text-gray-400 hover:text-rose-500 duration-500"
74 | >
75 | remove
76 |
77 |
78 |
79 |
80 | {currencyFormatter(product.price)}
81 |
82 |
86 | decreaseHandler(product)}
88 | className="w-10 h-10 bg-gray-100 border border-gray-300 flex justify-center items-center active:bg-gray-700 active:text-gray-50 font-semibold"
89 | >
90 | -
91 |
92 |
93 | {product.cartQuantity}
94 |
95 | increaseHandler(product)}
97 | className="w-10 h-10 bg-gray-100 border border-gray-300 flex justify-center items-center active:bg-gray-700 active:text-gray-50 font-semibold"
98 | >
99 | +
100 |
101 |
102 |
103 | {currencyFormatter(product.price * product.cartQuantity)}
104 |
105 |
106 | ))}
107 |
108 |
109 |
110 |
dispatch(clearCart())}
112 | className="clear-cart uppercase border py-2 px-4 hover:bg-red-300 hover:text-rose-600 hover:border-red-300 duration-500 font-medium"
113 | >
114 | clear cart
115 |
116 |
117 |
118 | subtotal
119 |
120 | {currencyFormatter(subtotal)}
121 |
122 |
123 |
124 | Taxes and shipping costs are calculated at the checkout
125 |
126 |
127 | checkout
128 |
129 |
133 |
134 |
135 |
136 |
continue shopping
137 |
138 |
139 |
140 | >
141 | )}
142 |
143 | );
144 | };
145 |
146 | export default Cart;
147 |
--------------------------------------------------------------------------------