├── public
├── _redirects
└── index.html
├── postcss.config.js
├── src
├── utilities
│ └── currencyFormatter.js
├── pages
│ ├── Home.js
│ ├── NotFound.js
│ ├── Products.js
│ └── Cart.js
├── components
│ ├── Footer.js
│ ├── Slide.js
│ ├── Navbar.js
│ ├── Card.js
│ └── Slider.js
├── app
│ └── store.js
├── index.js
├── features
│ └── products
│ │ ├── productSlice.js
│ │ └── cartSlice.js
├── App.js
└── index.css
├── tailwind.config.js
├── .gitignore
├── package.json
└── README.md
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/utilities/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.js:
--------------------------------------------------------------------------------
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.js:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 |
3 | import productsReducer, {
4 | productsFetching,
5 | } from "../features/products/productSlice";
6 | import cartReducer from "../features/products/cartSlice";
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productsReducer,
11 | cart: cartReducer,
12 | },
13 | });
14 |
15 | store.dispatch(productsFetching());
16 |
--------------------------------------------------------------------------------
/.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 | # local env files
12 | .env
13 |
14 | # production
15 |
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 | tech-alpha
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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/NotFound.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
11 |
12 | Page not found
13 |
14 |
15 |
16 | Go to home
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default NotFound;
24 |
--------------------------------------------------------------------------------
/src/pages/Products.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import Card from "../components/Card";
3 |
4 | const Products = () => {
5 | const { items: data, status } = useSelector((state) => state.products);
6 |
7 | return (
8 |
9 |
14 | Browse all products
15 |
16 |
17 | {status &&
{status}
}
18 |
19 | {data.map((product) => (
20 |
21 | ))}
22 |
23 |
24 | );
25 | };
26 |
27 | export default Products;
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 | export const productsFetching = createAsyncThunk(
10 | "products/productsFetching",
11 | async () => {
12 | const res = await axios.get(
13 | `${process.env.REACT_APP_BASE_URL}/api/products`
14 | );
15 | return res.data;
16 | }
17 | );
18 |
19 | export const productsSlice = createSlice({
20 | name: "products",
21 | initialState,
22 | reducers: {},
23 | extraReducers: (builder) => {
24 | builder.addCase(productsFetching.pending, (state, action) => {
25 | state.status = "Loading...";
26 | });
27 |
28 | builder.addCase(productsFetching.fulfilled, (state, action) => {
29 | state.status = "";
30 | state.items = action.payload;
31 | });
32 |
33 | builder.addCase(productsFetching.rejected, (state, action) => {
34 | state.status = "Something went wrong!";
35 | });
36 | },
37 | });
38 |
39 | export default productsSlice.reducer;
40 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import { ToastContainer } from "react-toastify";
3 |
4 | import Footer from "./components/Footer";
5 | import Navbar from "./components/Navbar";
6 | import Cart from "./pages/Cart";
7 | import Home from "./pages/Home";
8 | import NotFound from "./pages/NotFound";
9 | import Products from "./pages/Products";
10 | import "react-toastify/dist/ReactToastify.css";
11 | import { useEffect } from "react";
12 | import AOS from "aos";
13 | import "aos/dist/aos.css";
14 |
15 | const App = () => {
16 | useEffect(() => {
17 | AOS.init();
18 | }, []);
19 | return (
20 | <>
21 |
22 |
23 |
24 |
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 |
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export default App;
38 |
--------------------------------------------------------------------------------
/src/components/Slide.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Slide = ({ image }) => {
4 | return (
5 |
6 |
7 |
12 | {image.headline}
13 |
14 |
15 |
20 | {image.body}
21 |
22 |
28 |
29 | {image.cta}
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Slide;
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tech-alpha",
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 | "aos": "^2.3.4",
11 | "axios": "^1.2.2",
12 | "icons": "^1.0.0",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-icons": "^4.7.1",
16 | "react-redux": "^8.0.5",
17 | "react-router-dom": "^6.6.1",
18 | "react-scripts": "5.0.1",
19 | "react-toastify": "^9.1.1",
20 | "web-vitals": "^2.1.4"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | },
46 | "devDependencies": {
47 | "autoprefixer": "^10.4.13",
48 | "postcss": "^8.4.20",
49 | "tailwindcss": "^3.2.4"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
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 | tech Alpha
13 |
14 |
15 |
20 |
21 | Home
22 |
23 |
24 | Products
25 |
26 |
27 |
28 |
29 | {cartItems.length}
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Navbar;
39 |
--------------------------------------------------------------------------------
/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 | .wrapper {
18 | @apply container mx-auto px-5 lg:px-0;
19 | }
20 |
21 | /* Utilities */
22 | .space-font {
23 | font-family: "Space Grotesk", sans-serif;
24 | }
25 |
26 | /* Navbar */
27 | .nav-link {
28 | position: relative;
29 | }
30 |
31 | .nav-link::after {
32 | content: "";
33 | width: 0%;
34 | height: 1px;
35 | background-color: #f9fafb;
36 | position: absolute;
37 | left: 50%;
38 | transform: translateX(-50%);
39 | bottom: 0;
40 | transition: 0.3s;
41 | }
42 |
43 | .nav-link:hover::after {
44 | width: 100%;
45 | }
46 |
47 | /* slider */
48 | .slider {
49 | height: calc(100vh - 1rem);
50 | width: 500vw;
51 | display: flex;
52 | transition: 1s ease;
53 | }
54 |
55 | .slide {
56 | width: 100vw;
57 | height: 100%;
58 | background-position: center;
59 | background-repeat: no-repeat;
60 | background-size: cover;
61 | }
62 |
63 | /* Slide */
64 | .slide-btn {
65 | position: relative;
66 | }
67 |
68 | .slide-btn::before {
69 | content: "";
70 | position: absolute;
71 | z-index: 1;
72 | top: 0;
73 | left: 0;
74 | right: 0;
75 | bottom: 0;
76 | background-color: #f97316;
77 | width: 0%;
78 | transition: 0.3s ease;
79 | }
80 |
81 | .slide-btn:hover::before {
82 | width: 100%;
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tech Alpha
2 | Tech Alpha is an online e-commerce store that offers an intuitive shopping experience for its customers. The project is developed using React, Redux, and Tailwind CSS.
3 |
4 | ## Project Description
5 | Tech Alpha is an online e-commerce store built with a focus on providing a seamless shopping experience to its customers. The project has been developed using the latest front-end technologies, including React, Redux, Redux Thunk, and Redux Toolkit. Data fetching has been achieved using Axios, which ensures a fast and reliable source of data.
6 |
7 | ## Features
8 |
9 | - User Authentication: Secure user registration and login functionality.
10 | - Product Management: Add, edit, and delete electronic accessories products.
11 | - Shopping Cart: Implement a shopping cart for users to add and manage their selected items.
12 | - Order Processing: Enable users to place orders and track their order history.
13 | - MongoDB Integration: Store product data, user profiles, and order information in a MongoDB database.
14 | - Redux State Management: Utilize Redux for efficient state management.
15 | - Responsive Design: Implement a responsive user interface using Tailwind CSS.
16 | - Axios for HTTP Requests: Use Axios to fetch data from the server.
17 |
18 |
19 | ## Tools
20 | - React
21 | - Redux
22 | - Redux Thunk
23 | - Redux Toolkit
24 | - Axios
25 | - Tailwind CSS
26 |
27 | ## Installation
28 | 1. Clone the repository using the following command:
29 |
30 | `
31 | git clone https://github.com/kajal-rekha/tech-alpha.git
32 |
33 | `
34 |
35 | 2. Navigate to the project directory:
36 |
37 | `
38 | cd tech-alpha
39 | `
40 |
41 | 3. Install the required dependencies using the following command:
42 |
43 | `npm install`
44 |
45 |
46 | 4. Start the development server using the following command:
47 |
48 | `
49 | npm start
50 | `
51 |
52 | ## Links
53 |
54 | - [Live Demo](https://tech-alpha-ecommerce.netlify.app/)
55 |
56 | - [Frontend Repository](https://github.com/kajal-rekha/tech-alpha.git)
57 |
58 | - [Backend Repository](https://github.com/kajal-rekha/tech-alpha-server.git)
59 |
60 |
61 | ## Conclusion
62 | Tech Alpha is an online e-commerce store that is built using the latest front-end technologies. The project is focused on providing a seamless shopping experience to its customers. The project is open source, and contributions are welcome.
63 |
64 | If you have any questions or need assistance, please contact us at contact@tech-alpha.com.
65 |
66 | Happy shopping with Tech Alpha!
67 |
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { useNavigate } from "react-router-dom";
3 | import { addToCart } from "../features/products/cartSlice";
4 | import { currencyFormatter } from "../utilities/currencyFormatter";
5 |
6 | const Card = ({ product }) => {
7 | const navigate = useNavigate();
8 | const dispatch = useDispatch();
9 |
10 | const addtoCartHandler = (product) => {
11 | dispatch(addToCart(product));
12 | navigate("/cart");
13 | };
14 |
15 | return (
16 |
21 |
22 |
27 |
28 |
29 |
34 | {product.category}
35 |
36 |
41 | {product.name}
42 |
43 |
48 | {" "}
49 | {product.description}
50 |
51 |
52 |
57 | {currencyFormatter(product.price)}
58 |
59 | addtoCartHandler(product)}
61 | className="uppercase bg-violet-500 text-violet-50 font-medium py-3 px-8 rounded-md hover:bg-orange-500 hover:text-orange-50 duration-300 shadow-lg shadow-violet-300 hover:shadow-orange-300"
62 | data-aos="fade-up"
63 | data-aos-duration="2000"
64 | >
65 | Add to cart
66 |
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default Card;
74 |
--------------------------------------------------------------------------------
/src/components/Slider.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
3 |
4 | import Slide from "./Slide";
5 | const data = [
6 | {
7 | id: 1,
8 | src: "https://i.ibb.co/XszmG02/camera.jpg",
9 | headline: "DSLR cameras for stunning photos",
10 | body: "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.",
11 | cta: "Shop DSLR cameras now",
12 | category: "cameras",
13 | },
14 | {
15 | id: 2,
16 | src: "https://i.ibb.co/mtc8v16/tv.jpg",
17 | headline: "Upgrade your home entertainment with our TVs",
18 | body: "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.",
19 | cta: "Shop TVs and upgrade now",
20 | category: "tvs",
21 | },
22 | {
23 | id: 3,
24 | src: "https://i.ibb.co/kmr5qQv/headphones.jpg",
25 | headline: "Enhance your listening experience",
26 | body: "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.",
27 | cta: "Experience enhanced sound",
28 | category: "headphones",
29 | },
30 | {
31 | id: 4,
32 | src: "https://i.ibb.co/JqxDhvZ/console.jpg",
33 | headline: "Take your gaming to the next level",
34 | body: "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.",
35 | cta: "Shop consoles and play now",
36 | category: "consoles",
37 | },
38 | {
39 | id: 5,
40 | src: "https://media.wired.com/photos/650c80a777e4baffa125b82b/3:2/w_1280%2Cc_limit/Apple-Watch-SE-Gen-2-Featured-Gear.jpg",
41 | headline: "Stay connected with smart watches",
42 | body: "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.",
43 | cta: "Connect with a smart watch",
44 | category: "smart-watches",
45 | },
46 | ];
47 |
48 | const Slider = () => {
49 | const [currentSlide, setCurrentSlide] = useState(0);
50 |
51 | const prevSlide = () => {
52 | setCurrentSlide(
53 | currentSlide === 0 ? data.length - 1 : (prevSlide) => prevSlide - 1
54 | );
55 | };
56 |
57 | const nextSlide = () => {
58 | setCurrentSlide(
59 | currentSlide === data.length - 1 ? 0 : (prevSlide) => prevSlide + 1
60 | );
61 | };
62 |
63 | return (
64 |
65 |
69 | {data.map((image) => (
70 |
71 | ))}
72 |
73 |
74 |
75 |
81 |
82 |
83 |
84 |
85 |
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 |
9 | cartTotalAmount: 0,
10 | };
11 |
12 | const cartSlice = createSlice({
13 | name: "cart",
14 | initialState,
15 | reducers: {
16 | // addToCart
17 | addToCart(state, action) {
18 | const existedItemIndex = state.cartItems.findIndex(
19 | (item) => item._id === action.payload._id
20 | );
21 |
22 | // if exist
23 | if (existedItemIndex >= 0) {
24 | // increase quantity
25 | state.cartItems[existedItemIndex].cartQuantity += 1;
26 |
27 | //React Toastify added
28 | toast.info("Quantity increased!", {
29 | position: "bottom-left",
30 | autoClose: 5000,
31 | hideProgressBar: false,
32 | closeOnClick: true,
33 | pauseOnHover: true,
34 | draggable: true,
35 | progress: undefined,
36 | theme: "light",
37 | });
38 | } else {
39 | // add to cart
40 | const assembledItem = { ...action.payload, cartQuantity: 1 };
41 | state.cartItems.push(assembledItem);
42 |
43 | //React Toastify added
44 | toast.success("Product added into cart!", {
45 | position: "bottom-left",
46 | autoClose: 5000,
47 | hideProgressBar: false,
48 | closeOnClick: true,
49 | pauseOnHover: true,
50 | draggable: true,
51 | progress: undefined,
52 | theme: "light",
53 | });
54 | }
55 | // add to local storage
56 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
57 | },
58 |
59 | // remove from cart
60 | removeFromCart(state, action) {
61 | const updatedCartItems = state.cartItems.filter(
62 | (item) => item._id !== action.payload._id
63 | );
64 | state.cartItems = updatedCartItems;
65 |
66 | //React Toastify added
67 | toast.warn("Product removed from cart!", {
68 | position: "bottom-left",
69 | autoClose: 5000,
70 | hideProgressBar: false,
71 | closeOnClick: true,
72 | pauseOnHover: true,
73 | draggable: true,
74 | progress: undefined,
75 | theme: "light",
76 | });
77 | // update local storage
78 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
79 | },
80 |
81 | // clear cart
82 | clearCart(state, action) {
83 | state.cartItems = [];
84 |
85 | //React Toastify added
86 | toast.error("Cart cleared!", {
87 | position: "bottom-left",
88 | autoClose: 5000,
89 | hideProgressBar: false,
90 | closeOnClick: true,
91 | pauseOnHover: true,
92 | draggable: true,
93 | progress: undefined,
94 | theme: "light",
95 | });
96 |
97 | // update local storage
98 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
99 | },
100 |
101 | // decrease cart
102 | decreaseCart(state, action) {
103 | const itemIndex = state.cartItems.findIndex(
104 | (item) => item._id === action.payload._id
105 | );
106 |
107 | //if exist
108 | if (state.cartItems[itemIndex].cartQuantity > 1) {
109 | state.cartItems[itemIndex].cartQuantity -= 1;
110 |
111 | //React Toastify added
112 | toast.info("Quantity decreased!", {
113 | position: "bottom-left",
114 | autoClose: 5000,
115 | hideProgressBar: false,
116 | closeOnClick: true,
117 | pauseOnHover: true,
118 | draggable: true,
119 | progress: undefined,
120 | theme: "light",
121 | });
122 | } else if (state.cartItems[itemIndex].cartQuantity === 1) {
123 | const updatedCartItems = state.cartItems.filter(
124 | (item) => item._id !== action.payload._id
125 | );
126 | state.cartItems = updatedCartItems;
127 |
128 | //React Toastify added
129 | toast.warn("Product removed from cart!", {
130 | position: "bottom-left",
131 | autoClose: 5000,
132 | hideProgressBar: false,
133 | closeOnClick: true,
134 | pauseOnHover: true,
135 | draggable: true,
136 | progress: undefined,
137 | theme: "light",
138 | });
139 | }
140 | // update local storage
141 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
142 | },
143 |
144 | // get subtotal
145 | getSubtotal(state, action) {
146 | const Subtotal = state.cartItems.reduce((acc, item) => {
147 | const { price, cartQuantity } = item;
148 | const itemTotal = price * cartQuantity;
149 | acc += itemTotal;
150 |
151 | return acc;
152 | }, 0);
153 |
154 | state.cartTotalAmount = Subtotal;
155 | },
156 | },
157 | });
158 |
159 | export const {
160 | addToCart,
161 | removeFromCart,
162 | clearCart,
163 | decreaseCart,
164 | getSubtotal,
165 | } = cartSlice.actions;
166 | export default cartSlice.reducer;
167 |
--------------------------------------------------------------------------------
/src/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { Link } from "react-router-dom";
4 | import {
5 | clearCart,
6 | removeFromCart,
7 | decreaseCart,
8 | addToCart,
9 | getSubtotal,
10 | } from "../features/products/cartSlice";
11 | import { currencyFormatter } from "../utilities/currencyFormatter";
12 |
13 | const Cart = () => {
14 | const { cartItems: data, cartTotalAmount: subtotal } = useSelector(
15 | (state) => state.cart
16 | );
17 |
18 | const dispatch = useDispatch();
19 |
20 | const handleRemove = (product) => {
21 | dispatch(removeFromCart(product));
22 | };
23 |
24 | const handleDecrease = (product) => {
25 | dispatch(decreaseCart(product));
26 | };
27 |
28 | const handleIncrease = (product) => {
29 | dispatch(addToCart(product));
30 | };
31 |
32 | useEffect(() => {
33 | dispatch(getSubtotal());
34 | }, [data, dispatch]);
35 | return (
36 |
37 |
38 | {data.length > 0
39 | ? `You've added ${data.length} item${data.length > 1 ? "s" : ""}`
40 | : "Your cart is empty"}
41 |
42 |
43 | {data.length === 0 && (
44 |
45 | ⬅ Start shopping now
46 |
47 | )}
48 |
49 |
50 | {data.length > 0 && (
51 | <>
52 |
53 |
54 |
Products
55 |
Unit Price
56 |
Quantity
57 |
Total Price
58 |
59 |
60 |
61 | {data?.map((product) => (
62 |
66 |
67 |
72 |
73 | {product.name}
74 | handleRemove(product)}
76 | className="uppercase text-gray-400 hover:text-rose-500 duration-300"
77 | >
78 | Remove
79 |
80 |
81 |
82 |
83 | {currencyFormatter(product.price)}
84 |
85 |
86 | handleDecrease(product)}
88 | className="h-8 md:h-10 w-8 md:w-10 bg-gray-100 border border-gray-300 active:bg-gray-700 "
89 | >
90 | -
91 |
92 |
93 | {" "}
94 | {product.cartQuantity}
95 |
96 |
97 | handleIncrease(product)}
99 | className="h-8 md:h-10 w-8 md:w-10 bg-gray-100 border border-gray-300 active:bg-gray-700"
100 | >
101 | +
102 |
103 |
104 |
105 | {currencyFormatter(product.price * product.cartQuantity)}
106 |
107 |
108 | ))}
109 |
110 |
111 |
112 |
113 |
dispatch(clearCart())}
115 | className="clear-btn uppercase border py-3 px-8 hover:bg-rose-200 hover:text-rose-600 font-medium text-sm md:text-lg hover:border-rose-200 duration-300"
116 | >
117 | Clear cart
118 |
119 |
120 |
121 | Subtotal
122 |
123 | {" "}
124 | {currencyFormatter(subtotal)}
125 |
126 |
127 |
128 | Taxes and shipping costs are calculated at the checkout
129 |
130 |
134 | Chechout
135 |
136 |
140 | {" "}
141 | ⬅ Continue shopping
142 |
143 |
144 |
145 | >
146 | )}
147 |
148 | );
149 | };
150 |
151 | export default Cart;
152 |
--------------------------------------------------------------------------------