├── public
├── favicon.ico
└── index.html
├── postcss.config.js
├── src
├── pages
│ ├── NotFound.js
│ ├── Home.js
│ ├── Checkout.js
│ ├── Products.js
│ └── Cart.js
├── utilities
│ └── CurrencyFormatter.js
├── components
│ ├── Footer.js
│ ├── Slide.js
│ ├── Navbar.js
│ ├── Card.js
│ └── Slider.js
├── app
│ └── store.js
├── index.js
├── App.js
├── features
│ └── products
│ │ ├── productSlice.js
│ │ └── cartSlice.js
└── index.css
├── tailwind.config.js
├── .gitignore
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasimhelal/techalpha/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | const NotFound = () => {
2 | return
NotFound
;
3 | };
4 |
5 | export default NotFound;
6 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | };
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
4 |
©{new Date().getFullYear()} | All rights reserved
5 |
6 | );
7 | };
8 |
9 | export default Footer;
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | # production
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | techalpha | get the best gadgets form here
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import cartReducer from "../features/products/cartSlice";
2 | import productReducer, {
3 | fetchedProduct,
4 | } from "../features/products/productSlice";
5 |
6 | const { configureStore } = require("@reduxjs/toolkit");
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productReducer,
11 | cart: cartReducer,
12 | },
13 | });
14 |
15 | store.dispatch(fetchedProduct());
16 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { Provider } from "react-redux";
4 | import { BrowserRouter } from "react-router-dom";
5 | import App from "./App";
6 | import { store } from "./app/store";
7 | import "./index.css";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/src/pages/Checkout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import { BsArrowLeft } from "react-icons/bs";
4 | const Checkout = () => {
5 | return (
6 |
7 |
8 | I will develop this page when we learn firebase authetication
9 |
10 |
14 |
15 |
Continue shopping
16 |
17 |
18 | );
19 | };
20 |
21 | export default Checkout;
22 |
--------------------------------------------------------------------------------
/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 |
10 | Browse all products
11 |
12 |
13 | {status &&
{status}
}
14 | {data.map((product) => (
15 |
16 | ))}
17 |
18 |
19 | );
20 | };
21 |
22 | export default Products;
23 |
--------------------------------------------------------------------------------
/src/components/Slide.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Slide = ({ image }) => {
4 | return (
5 |
10 |
11 |
12 | {image.headline}
13 |
14 |
{image.body}
15 |
19 | {image.cta}
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default Slide;
27 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import Footer from "./components/Footer";
3 | import Navbar from "./components/Navbar";
4 | import Cart from "./pages/Cart";
5 | import Home from "./pages/Home";
6 | import Products from "./pages/Products";
7 | import NotFound from "./pages/NotFound";
8 | import Checkout from "./pages/Checkout";
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 |
32 | export default App;
33 |
--------------------------------------------------------------------------------
/src/features/products/productSlice.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
2 | import axios from "axios";
3 |
4 | export const fetchedProduct = createAsyncThunk(
5 | "products/fetchedProduct", // slice's name property / actionName=) products/fetchedProduct
6 | async () => {
7 | const response = await axios.get(
8 | "https://eager-sable-airedale.glitch.me/products"
9 | );
10 | return response.data;
11 | }
12 | );
13 |
14 | export const productSlice = createSlice({
15 | name: "products",
16 | initialState: {
17 | items: [],
18 | status: null,
19 | },
20 | reducers: {},
21 | extraReducers: (builder) => {
22 | builder.addCase(fetchedProduct.pending, (state, action) => {
23 | state.status = "Loading...";
24 | });
25 |
26 | builder.addCase(fetchedProduct.fulfilled, (state, action) => {
27 | state.status = "";
28 | state.items = action.payload; // [...state,action.payload] is running in bg
29 | });
30 |
31 | builder.addCase(fetchedProduct.rejected, (state, action) => {
32 | state.status = "Something went wrong";
33 | });
34 | },
35 | });
36 |
37 | const productReducer = productSlice.reducer;
38 | export default productReducer;
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "techalpha",
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 | "redux": "^4.2.0",
19 | "web-vitals": "^2.1.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "autoprefixer": "^10.4.13",
47 | "postcss": "^8.4.20",
48 | "tailwindcss": "^3.2.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/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 | 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;500;600;700&display=swap');
7 |
8 | /* ulitilities */
9 | .space-font {
10 | font-family: 'Space Grotesk', sans-serif;
11 | }
12 |
13 |
14 |
15 | body {
16 | font-family: 'Poppins', sans-serif;
17 | position: relative;
18 | }
19 |
20 | .nav-link {
21 | position: relative;
22 | }
23 |
24 | .nav-link:before {
25 | position: absolute;
26 | content: '';
27 | bottom: 0;
28 | left: 0;
29 | width: 100%;
30 | background-color: #f9fafb;
31 | height: 1px;
32 | transition: 300ms;
33 | transform-origin: center;
34 | transform: scaleX(0);
35 | }
36 |
37 | .right {
38 | font-weight: 300;
39 | }
40 |
41 | .nav-link:hover:before {
42 | transform: scaleX(1);
43 | }
44 |
45 | .slider {
46 | height: calc(100vh - 5rem);
47 | width: 500vw;
48 | display: flex;
49 | transition: 1s ease;
50 | }
51 |
52 | .slide {
53 | width: 100vw;
54 | height: 100%;
55 | background-position: center;
56 | background-repeat: no-repeat;
57 | background-size: cover;
58 | }
59 |
60 | /* slide cta */
61 | .slide-cta {
62 | position: relative;
63 | z-index: 0;
64 |
65 | }
66 |
67 | .slide-cta:after {
68 | position: absolute;
69 | content: '';
70 | width: 100%;
71 | height: 100%;
72 | background: #f97316;
73 | left: 0;
74 | z-index: -1;
75 | top: 0;
76 | transition: 300ms;
77 | transform: scaleX(0);
78 | transform-origin: left;
79 | }
80 |
81 | .slide-cta:hover::after {
82 | transform: scaleX(1);
83 | }
--------------------------------------------------------------------------------
/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 |
17 |
18 |
19 |
20 | {product.category}
21 |
22 |
23 | {product.name}
24 |
25 |
26 | {product.description}
27 |
28 |
29 |
30 | {currencyFormatter(product.price)}
31 |
32 | addToCartHandler(product)}
34 | className="uppercase rounded-md shadow-md shadow-violet-500 hover:shadow-orange-500 bg-violet-500 text-violet-50 hover:bg-orange-500 hover:text-orange-50 font-medium py-3 px-8 duration-300"
35 | >
36 | Add to cart
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default Card;
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TechAlpha : An electronic accessories store
2 |
3 | ## Project Description
4 | TechAlpha is an online e-commerce store built with the latest front-end technologies. The aim of this project is to provide a dynamic and user-friendly shopping experience for customers.
5 |
6 | An online electronics accessories store built with React, Redux, Tailwind CSS, Redux Thunk, and Redux Toolkit. This e-shop allows customers to browse and purchase various electronic accessories such as phone cases, chargers, cables, and more. The user-friendly interface, powered by React, is accompanied by the sleek styling provided by Tailwind CSS. The efficient management of state and asynchronous actions is handled by Redux and its related packages, ensuring a seamless shopping experience for the customer.
7 |
8 | ## Project Features
9 | - Browse and purchase various electronics accessories such as phone cases, chargers, cables, and more.
10 | - Dynamic and user-friendly interface powered by React.
11 | - Sleek and visually appealing layout provided by Tailwind CSS.
12 | - Efficient state management and handling of asynchronous actions with Redux, Redux Thunk, and Redux Toolkit.
13 | - Data is fetched using Axios.
14 |
15 | ## Tools
16 | - React
17 | - Redux
18 | - Redux Thunk
19 | - Redux Toolkit
20 | - Axios
21 | - Tailwind CSS
22 |
23 | ## Installation
24 | 1. Clone this repository: `git clone https://github.com/[user-name]/techalpha.git`
25 | 2. Navigate to the project directory: `cd techalpha`
26 | 3. Install all dependencies: `npm install`
27 | 4. Start the development server: `npm start`
28 |
29 | ## Conclusion
30 | TechAlpha is a great example of a modern e-commerce website built with the latest front-end technologies. The use of React, Redux, and Tailwind CSS makes for a dynamic and visually appealing user experience, while the implementation of Redux Thunk and Redux Toolkit ensures efficient state management and handling of asynchronous actions. This project serves as a solid starting point for anyone looking to build their own e-commerce store.
31 |
32 | **Note:** This is only the front-end implementation of the project. The back-end is not included in this repository.
33 |
--------------------------------------------------------------------------------
/src/components/Slider.js:
--------------------------------------------------------------------------------
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 | 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://images.pexels.com/photos/1697214/pexels-photo-1697214.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
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 | const Slider = () => {
48 | const [currentSlide, setCurrentSlide] = useState(1);
49 |
50 | const prevSlide = () => {
51 | setCurrentSlide(
52 | currentSlide === 0 ? data.length - 1 : (prevSlide) => prevSlide - 1
53 | );
54 | };
55 | const nextSlide = () => {
56 | setCurrentSlide(
57 | currentSlide === data.length - 1 ? 0 : (prevSlide) => prevSlide + 1
58 | );
59 | };
60 |
61 | return (
62 |
63 |
67 | {data.map((image) => (
68 |
69 | ))}
70 |
71 |
72 |
73 |
77 |
78 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | export default Slider;
97 |
--------------------------------------------------------------------------------
/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 | cartTotalQuantity: 0,
9 | cartTotalAmout: 0,
10 | };
11 | const cartSlice = createSlice({
12 | name: "cart",
13 | initialState,
14 | reducers: {
15 | addToCart(state, action) {
16 | //check for existing product in cart
17 | const existingItemIndex = state.cartItems.findIndex(
18 | (item) => item.id === action.payload.id
19 | );
20 | //if exist
21 | if (existingItemIndex >= 0) {
22 | state.cartItems[existingItemIndex].cartQuantity += 1;
23 | toast.info("Quantity Increased", {
24 | position: "bottom-left",
25 | autoClose: 5000,
26 | hideProgressBar: false,
27 | closeOnClick: true,
28 | pauseOnHover: true,
29 | draggable: true,
30 | progress: undefined,
31 | theme: "light",
32 | });
33 | }
34 | //if not exist -->
35 | else {
36 | //add to cart
37 | const assembledItem = { ...action.payload, cartQuantity: 1 };
38 | state.cartItems.push(assembledItem);
39 | toast.success("Product Added Successfully", {
40 | position: "bottom-left",
41 | autoClose: 5000,
42 | hideProgressBar: false,
43 | closeOnClick: true,
44 | pauseOnHover: true,
45 | draggable: true,
46 | progress: undefined,
47 | theme: "light",
48 | });
49 | }
50 | //add to localStorage
51 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
52 | },
53 |
54 | removeFromCart(state, action) {
55 | const updatedCartItems = state.cartItems.filter(
56 | (item) => item.id !== action.payload.id
57 | );
58 | state.cartItems = updatedCartItems;
59 | toast.error("Product Removed", {
60 | position: "bottom-left",
61 | autoClose: 5000,
62 | hideProgressBar: false,
63 | closeOnClick: true,
64 | pauseOnHover: true,
65 | draggable: true,
66 | progress: undefined,
67 | theme: "light",
68 | });
69 | //update local storage
70 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
71 | },
72 | clearCart(state, action) {
73 | state.cartItems = [];
74 | toast.error("Cart Cleared", {
75 | position: "bottom-left",
76 | autoClose: 5000,
77 | hideProgressBar: false,
78 | closeOnClick: true,
79 | pauseOnHover: true,
80 | draggable: true,
81 | progress: undefined,
82 | theme: "light",
83 | });
84 | //update local storage
85 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
86 | },
87 | decreseCartQuantity(state, action) {
88 | const itemIndex = state.cartItems.findIndex(
89 | (item) => item.id === action.payload.id
90 | );
91 | //if exist item quantity is greater than one
92 | if (state.cartItems[itemIndex].cartQuantity > 1) {
93 | state.cartItems[itemIndex].cartQuantity -= 1;
94 | toast.info("Quantity Decrease", {
95 | position: "bottom-left",
96 | autoClose: 5000,
97 | hideProgressBar: false,
98 | closeOnClick: true,
99 | pauseOnHover: true,
100 | draggable: true,
101 | progress: undefined,
102 | theme: "light",
103 | });
104 | }
105 | //if exist item quantity is one make remove
106 | else if (state.cartItems[itemIndex].cartQuantity === 1) {
107 | const updatedCartItems = state.cartItems.filter(
108 | (item) => item.id !== action.payload.id
109 | );
110 | state.cartItems = updatedCartItems;
111 | toast.error("Product Removed", {
112 | position: "bottom-left",
113 | autoClose: 5000,
114 | hideProgressBar: false,
115 | closeOnClick: true,
116 | pauseOnHover: true,
117 | draggable: true,
118 | progress: undefined,
119 | theme: "light",
120 | });
121 | //update local storage
122 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
123 | }
124 | },
125 |
126 | getSubtalPrice(state, action) {
127 | const subtotal = state.cartItems.reduce((acc, item) => {
128 | const { price, cartQuantity } = item;
129 | const itemTotalPrice = price * cartQuantity;
130 | acc += itemTotalPrice;
131 | return acc;
132 | }, 0);
133 |
134 | state.cartTotalAmout = subtotal;
135 | },
136 | },
137 | });
138 | //actions
139 | export const {
140 | addToCart,
141 | removeFromCart,
142 | clearCart,
143 | decreseCartQuantity,
144 | getSubtalPrice,
145 | } = cartSlice.actions;
146 |
147 | //reducer
148 | const cartReducer = cartSlice.reducer;
149 | export default cartReducer;
150 |
--------------------------------------------------------------------------------
/src/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { currencyFormatter } from "../utilities/CurrencyFormatter.js";
3 | import Products from "./Products.js";
4 | import { BsArrowLeft } from "react-icons/bs";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import {
7 | addToCart,
8 | clearCart,
9 | decreseCartQuantity,
10 | getSubtalPrice,
11 | removeFromCart,
12 | } from "../features/products/cartSlice.js";
13 | import { useEffect } from "react";
14 |
15 | const Cart = () => {
16 | const { cartItems: data, cartTotalAmout: subtotal } = useSelector(
17 | (state) => state.cart
18 | );
19 | const dispatch = useDispatch();
20 |
21 | useEffect(() => {
22 | dispatch(getSubtalPrice());
23 | }, [data, dispatch]);
24 |
25 | return (
26 |
27 |
28 |
29 | {data.length > 0
30 | ? `You have ${data.length} cart item${data.length > 1 ? "s" : ""}`
31 | : "Your cart is empty"}
32 |
33 | {data.length === 0 && (
34 |
}
37 | className="flex items-center gap-3 text-teal-400 justify-center"
38 | >
39 |
40 |
Continue shopping
41 |
42 | )}
43 | {data.length > 0 && (
44 | <>
45 |
46 |
Products
47 |
unit price
48 |
quantity
49 |
total price
50 |
51 |
52 |
53 | {data?.map((product) => (
54 |
58 |
59 |
64 |
65 |
{product.name}
66 | dispatch(removeFromCart(product))}
68 | className="hover:text-rose-500 uppercase duration-300 font-medium"
69 | >
70 | remove
71 |
72 |
73 |
74 |
75 |
76 | {currencyFormatter(product.price)}
77 |
78 |
79 |
80 | dispatch(decreseCartQuantity(product))}
82 | className="w-10 h-10 bg-gray-200 border border-gray-300 active:bg-gray-700 active:text-gray-50"
83 | >
84 | -
85 |
86 |
87 | {product.cartQuantity}
88 |
89 | dispatch(addToCart(product))}
91 | className="w-10 h-10 bg-gray-200 border border-gray-300 active:bg-gray-700 active:text-gray-50"
92 | >
93 | +
94 |
95 |
96 |
97 |
98 | {currencyFormatter(product.price * product.cartQuantity)}
99 |
100 |
101 | ))}
102 |
103 |
104 |
105 |
106 | dispatch(clearCart())}
108 | className="bg-rose-50 hover:bg-rose-200 duration-300 text-rose-400 hover:text-red-500 uppercase font-medium tracking-widest py-3 px-6 border border-rose-300"
109 | >
110 | clear chat
111 |
112 |
113 |
114 |
115 | subtotal
116 | {currencyFormatter(subtotal)}
117 |
118 |
119 | Taxes and shipping costs are calculated at the checkout
120 |
121 |
125 | cheackout
126 |
127 |
}
130 | className="flex items-center gap-3 text-teal-400"
131 | >
132 |
133 |
Continue shopping
134 |
135 |
136 |
137 | >
138 | )}
139 |
140 |
141 | );
142 | };
143 |
144 | export default Cart;
145 |
--------------------------------------------------------------------------------