├── public
├── _redirects
└── index.html
├── src
├── components
│ ├── CheckOutSuccess.jsx
│ ├── Footer.js
│ ├── Slide.js
│ ├── PayButton.jsx
│ ├── Navbar.js
│ ├── Card.js
│ └── Slider.js
├── utlities
│ └── currencyFormatter.js
├── pages
│ ├── Home.js
│ ├── NotFound.js
│ ├── Products.js
│ └── Cart.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
--------------------------------------------------------------------------------
/src/components/CheckOutSuccess.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const CheckOutSuccess = () => {
4 | return (
5 |
Check Out Success
6 | )
7 | }
8 |
9 | export default CheckOutSuccess
--------------------------------------------------------------------------------
/src/utlities/currencyFormatter.js:
--------------------------------------------------------------------------------
1 | export const currencyFormatter = (price) => {
2 | if (!price) return;
3 | return price.toLocaleString("en-us", {
4 | style: "currency",
5 | currency: "USD",
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [require('@tailwindcss/line-clamp'),],
10 | }
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Slider from '../components/Slider'
3 | import Products from './Products'
4 |
5 | const Home = () => {
6 | return (
7 |
11 | )
12 | }
13 |
14 | export default Home
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tech alpha
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Footer = () => {
4 | return (
5 |
6 |
© {new Date().getFullYear}
7 | Tech Alpha. All rights reserved
8 |
9 |
10 | )
11 | }
12 |
13 | export default Footer
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore} from "@reduxjs/toolkit";
2 | import productsReducer,{productsFetching} from "../features/products/productSlice"
3 | import cartReducer from "../features/products/cartSlice"
4 |
5 | export const store= configureStore({
6 | reducer:{
7 | products:productsReducer,
8 | cart: cartReducer
9 | }
10 | })
11 |
12 | store.dispatch(productsFetching())
--------------------------------------------------------------------------------
/.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 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom"
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
7 | Page not found!
8 |
9 |
13 | Go home
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default NotFound
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import { BrowserRouter } from 'react-router-dom';
6 |
7 | import {store} from './app/store';
8 | import {Provider} from 'react-redux'
9 |
10 |
11 | const root = ReactDOM.createRoot(document.getElementById('root'));
12 | root.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/src/pages/Products.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Card from '../components/Card'
4 |
5 |
6 |
7 | const Products = () => {
8 |
9 | //redux state access
10 | const {items:data, status}= useSelector((state)=>state.products)
11 | console.log(data)
12 |
13 | return
14 |
Browse all Products
15 |
16 | {status &&
{status}
}
17 | {data.map((product)=>(
18 |
19 | ))}
20 |
21 |
22 | }
23 |
24 | export default Products
--------------------------------------------------------------------------------
/src/components/Slide.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Slide = ({ image }) => {
4 | return (
5 |
6 |
7 |
8 | {image.headline}
9 |
10 |
{image.body}
11 |
15 |
16 | {image.cta}
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Slide;
--------------------------------------------------------------------------------
/src/components/PayButton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import axios from "axios";
3 |
4 | const PayButton = ({ data }) => {
5 | const handleCheckout = async () => {
6 | // https://tech-alpha-qtwm.onrender.com/api
7 | // http://localhost:4000/api/
8 | axios
9 | .post(`https://tech-alpha-qtwm.onrender.com/api/stripe/create-checkout-session`, {
10 | data,
11 | userId: 3,
12 | })
13 | .then((response) => {
14 | if (response.data.url) {
15 | console.log(response.data.url)
16 | window.location.href = response.data.url;
17 | }
18 | })
19 | .catch((err) => console.log(err.message));
20 | };
21 |
22 | return (
23 | <>
24 |
30 | >
31 | );
32 | };
33 |
34 | export default PayButton;
35 |
--------------------------------------------------------------------------------
/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 | // https://tech-alpha-qtwm.onrender.com/api/products
11 | export const productsFetching = createAsyncThunk(
12 | "Products/productsFetching",
13 | async () => {
14 | const res = await axios.get(
15 | "https://tech-alpha-qtwm.onrender.com/api/products"
16 | );
17 | return res.data;
18 | }
19 | );
20 |
21 | export const productSlice = createSlice({
22 | name: "Products",
23 | initialState,
24 | reducers: {},
25 | extraReducers: (builder) => {
26 | builder.addCase(productsFetching.pending, (state, action) => {
27 | state.status = "Loading .......";
28 | });
29 | builder.addCase(productsFetching.fulfilled, (state, action) => {
30 | state.status = "";
31 | state.items = action.payload;
32 | });
33 | builder.addCase(productsFetching.rejected, (state, action) => {
34 | state.status = "Something went wrong";
35 | });
36 | },
37 | });
38 |
39 | export default productSlice.reducer;
40 |
--------------------------------------------------------------------------------
/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 | "axios": "^1.6.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/line-clamp": "^0.4.4",
46 | "tailwindcss": "^3.2.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from 'react-router-dom'
2 | import Navbar from './components/Navbar'
3 | import Footer from './components/Footer'
4 | import Home from './pages/Home'
5 | import Products from './pages/Products'
6 | import NotFound from './pages/NotFound'
7 | import Cart from './pages/Cart'
8 | import { ToastContainer } from 'react-toastify'
9 | import 'react-toastify/dist/ReactToastify.min.css';
10 | import CheckOutSuccess from './components/CheckOutSuccess'
11 |
12 |
13 | const App = () => {
14 |
15 | const isNavActiveStyles=({isActive})=>{
16 | return {
17 | color: isActive ? "#f97316":null,
18 | };
19 | }
20 |
21 | return (
22 | <>
23 |
24 |
25 |
26 |
27 | }/>
28 | }/>
29 | }/>
30 | } />
31 | } />
32 | }/>
33 |
34 |
35 |
36 | >
37 | )
38 | }
39 |
40 | export default App
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 |
2 | import { Link,NavLink } from "react-router-dom";
3 | import { BsCart3 } from "react-icons/bs";
4 | import { useSelector } from "react-redux";
5 |
6 |
7 | const Navbar = ({ isNavActiveStyles }) => {
8 |
9 | const { cartItems } = useSelector((state) => state.cart);
10 |
11 | return (
12 |
13 |
14 |
15 |
16 | tech
17 | Alpha
18 |
19 |
20 |
21 |
22 | Home
23 |
24 |
25 | Products
26 |
27 |
28 |
29 |
30 |
31 |
32 | {cartItems.length}
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default Navbar;
43 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
6 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap');
7 | @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
8 |
9 | body{
10 | font-family: 'Poppins', sans-serif;
11 | overflow-x: hidden;
12 | }
13 |
14 |
15 | /* Utilities */
16 | .space-font {
17 | font-family: "Space Grotesk", sans-serif;
18 | }
19 |
20 | /* Navbar */
21 | .nav-link{
22 | position: relative;
23 | }
24 | .nav-link::after{
25 | content: "";
26 | width: 0%;
27 | height: 1px;
28 | background-color: #f9fafb;
29 | position: absolute;
30 | left: 50%;
31 | bottom: 0;
32 | transform: translate(-50%);
33 | transition: 0.3s;
34 | }
35 | .nav-link:hover::after{
36 | width: 100%;
37 | }
38 |
39 |
40 | /* Slider */
41 | .slider{
42 | height: calc(100vh - 5rem);
43 | width: 500vw;
44 | display: flex;
45 | transition: 1s ease;
46 | }
47 | .slide{
48 | width: 100vw;
49 | height: 100%;
50 | background-position: center;
51 | background-repeat: no-repeat;
52 | background-size: cover;
53 | }
54 |
55 |
56 | /* Slide */
57 | .slide-btn {
58 | position: relative;
59 | }
60 |
61 | .slide-btn::before {
62 | content: "";
63 | position: absolute;
64 | z-index: 1;
65 | top: 0;
66 | left: 0;
67 | right: 0;
68 | bottom: 0;
69 | background-color: #f97316;
70 | width: 0%;
71 | transition: 0.3s ease;
72 | }
73 |
74 | .slide-btn:hover::before {
75 | width: 100%;
76 | }
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch } from "react-redux";
3 | import { useNavigate } from "react-router-dom";
4 | import { addToCart } from "../features/products/cartSlice";
5 | import { currencyFormatter } from "../utlities/currencyFormatter";
6 |
7 | const Card = ({ product }) => {
8 | const dispatch = useDispatch();
9 | const navigate = useNavigate();
10 |
11 | const addToCardHandler = (product) => {
12 | dispatch(addToCart(product))
13 | navigate("/cart");
14 | };
15 |
16 | return (
17 |
18 |
19 |

20 |
21 |
22 |
23 | {product.category}
24 |
25 |
26 | {product.name}
27 |
28 |
{product.description}
29 |
30 |
31 | {currencyFormatter(product.price)}
32 |
33 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default Card;
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Tech Alpha
2 |
3 | Tech-Alpha Ecommerce is an advanced online shopping platform powered by the MERN (MongoDB, Express, React, Node.js) stack. It enables users to enjoy a seamless shopping experience, with a dedicated cart page that allows effortless product additions. The integration of Redux state management ensures a high level of efficiency and flexibility.
4 |
5 | ## Features
6 | - State management using Redux Toolkit
7 | - Fetches product data using Redux thunk.
8 | - A shopping cart that uses Redux Toolkit for state management.
9 | - The cart items are saved in the client's browser local storage
10 | - Styling using Tailwind CSS
11 |
12 | ## Tools Used
13 |
14 | - Frontend: React, Redux
15 | - Backend: Node.js, Express
16 | - Database: MongoDB
17 | - State Management: Redux
18 | - UI Styling: TailwindCSS
19 |
20 | ## Installation (Client)
21 |
22 | 1. Clone this repository: `git clone https://github.com/Rafiul29/tech-alpha-client.git`
23 | 2. Navigate to the client directory: `cd tech-alpha-client`
24 | 3. Install dependencies: `npm install`
25 | 4. Start the client: `npm start`
26 | 5. Access the client in your browser at: `http://localhost:3000`
27 |
28 | ## Installation (Server)
29 |
30 | 1. Clone this repository: `git clone https://github.com/Rafiul29/tech-alpha-server.git`
31 | 2. Navigate to the server directory: `cd tech-alpha-server`
32 | 3. Install dependencies: `npm install`
33 | 4. Start the server: `npm start`
34 | 5. Create a `.env` file in the server directory and set the necessary environment variables (e.g, MongoDB URI, PORT).
35 | - `MONGODB_URI`: the MongoDB connection string
36 | - `PORT`: a secret string for JWT authentication
37 | 6. The server will be running at: `http://localhost:5000`
38 |
39 | ## Links
40 |
41 | - [Live Demo](https://tech-alpha-nu.vercel.app/)
42 | - [Front-End Repository](https://github.com/Rafiul29/tech-alpha-client.git)
43 | - [Back-End Repository](https://github.com/Rafiul29/tech-alpha-server.git)
44 |
--------------------------------------------------------------------------------
/src/components/Slider.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { BsArrowRight, BsArrowLeft } from "react-icons/bs";
3 | import Slide from "./Slide";
4 | const data = [
5 | {
6 | id: 1,
7 | src: "https://i.ibb.co/XszmG02/camera.jpg",
8 | headline: "DSLR cameras for stunning photos",
9 | 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.",
10 | cta: "Shop DSLR cameras now",
11 | category: "cameras",
12 | },
13 | {
14 | id: 2,
15 | src: "https://i.ibb.co/mtc8v16/tv.jpg",
16 | headline: "Upgrade your home entertainment with our TVs",
17 | 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.",
18 | cta: "Shop TVs and upgrade now",
19 | category: "tvs",
20 | },
21 | {
22 | id: 3,
23 | src: "https://i.ibb.co/kmr5qQv/headphones.jpg",
24 | headline: "Enhance your listening experience",
25 | 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.",
26 | cta: "Experience enhanced sound",
27 | category: "headphones",
28 | },
29 | {
30 | id: 4,
31 | src: "https://i.ibb.co/JqxDhvZ/console.jpg",
32 | headline: "Take your gaming to the next level",
33 | 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.",
34 | cta: "Shop consoles and play now",
35 | category: "consoles",
36 | },
37 | {
38 | id: 5,
39 | src: "https://i.ibb.co/YbS7mL2/smart-watches.jpg",
40 | headline: "Stay connected with smart watches",
41 | 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.",
42 | cta: "Connect with a smart watch",
43 | category: "smart-watches",
44 | },
45 | ];
46 |
47 | const Slider = () => {
48 | const [currentSlide, setCurrentSlide] = useState(0);
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 |
81 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default Slider;
96 |
--------------------------------------------------------------------------------
/src/features/products/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { toast } from "react-toastify";
3 | const initialState = {
4 | cartItems: localStorage.getItem("cartItems")
5 | ? JSON.parse(localStorage.getItem("cartItems"))
6 | : [],
7 | cartTotalQuantity: 0,
8 | cartTotalAmount: 0,
9 | };
10 |
11 | const cartSlice = createSlice({
12 | name: "cart",
13 | initialState,
14 | reducers: {
15 | addToCart(state, action) {
16 | //check if the items is already in the cart
17 | const existedItemIndex = state.cartItems.findIndex(
18 | (item) => item._id === action.payload._id
19 | );
20 |
21 | // if exist
22 | if (existedItemIndex >= 0) {
23 | // increase quantity
24 | state.cartItems[existedItemIndex].cartQuantity += 1;
25 | toast.info('Quantity Increase', {
26 | position: "bottom-left",
27 | autoClose: 5000,
28 | hideProgressBar: false,
29 | closeOnClick: true,
30 | pauseOnHover: true,
31 | draggable: true,
32 | progress: undefined,
33 | theme: "light",
34 | });
35 | } else {
36 | // add to cart
37 | const assembledItem = { ...action.payload, cartQuantity: 1 };
38 | state.cartItems.push(assembledItem);
39 |
40 | toast.success('Product added into cart', {
41 | position: "bottom-left",
42 | autoClose: 5000,
43 | hideProgressBar: false,
44 | closeOnClick: true,
45 | pauseOnHover: true,
46 | draggable: true,
47 | progress: undefined,
48 | theme: "light",
49 | });
50 | }
51 | // add to local storage
52 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
53 | },
54 |
55 | // remove item
56 | removeFromCart(state, action) {
57 | const updatedCartItems = state.cartItems.filter(
58 | (item) => item._id !== action.payload._id
59 | );
60 | state.cartItems = updatedCartItems;
61 | //update local storage
62 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
63 |
64 | toast.warn('Product remove from cart!', {
65 | position: "bottom-left",
66 | autoClose: 5000,
67 | hideProgressBar: false,
68 | closeOnClick: true,
69 | pauseOnHover: true,
70 | draggable: true,
71 | progress: undefined,
72 | theme: "light",
73 | });
74 | },
75 |
76 | clearCart(state, action) {
77 | state.cartItems = [];
78 | //update local storage
79 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
80 | toast.error('cart clear', {
81 | position: "bottom-left",
82 | autoClose: 5000,
83 | hideProgressBar: false,
84 | closeOnClick: true,
85 | pauseOnHover: true,
86 | draggable: true,
87 | progress: undefined,
88 | theme: "light",
89 | });
90 | },
91 |
92 | decreaseCart(state, action) {
93 | const itemIndex = state.cartItems.findIndex(
94 | (item) => item._id === action.payload._id
95 | );
96 |
97 | //if exist
98 | if (state.cartItems[itemIndex].cartQuantity > 1) {
99 | state.cartItems[itemIndex].cartQuantity -= 1;
100 | toast.info('Quantity decrease', {
101 | position: "bottom-left",
102 | autoClose: 5000,
103 | hideProgressBar: false,
104 | closeOnClick: true,
105 | pauseOnHover: true,
106 | draggable: true,
107 | progress: undefined,
108 | theme: "light",
109 | });
110 | } else if (state.cartItems[itemIndex].cartQuantity === 1) {
111 | const updatedCartItems = state.cartItems.filter(
112 | (item) => item._id !== action.payload._id
113 | );
114 | state.cartItems = updatedCartItems;
115 | toast.warn('Product remove from cart!', {
116 | position: "bottom-left",
117 | autoClose: 5000,
118 | hideProgressBar: false,
119 | closeOnClick: true,
120 | pauseOnHover: true,
121 | draggable: true,
122 | progress: undefined,
123 | theme: "light",
124 | });
125 | }
126 |
127 | //update local storage
128 | localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
129 | },
130 |
131 | //total amount
132 |
133 | getSubtotal(state,action){
134 | const subtotal=state.cartItems.reduce((acc,item)=>{
135 | const {price,cartQuantity}=item;
136 | const itemTotal=price*cartQuantity;
137 | acc+=itemTotal;
138 | return acc
139 | },0)
140 | state.cartTotalAmount=subtotal;
141 | }
142 |
143 |
144 |
145 | },
146 | });
147 |
148 | export const { addToCart, removeFromCart, clearCart ,decreaseCart,getSubtotal} = cartSlice.actions;
149 |
150 | export default cartSlice.reducer;
151 |
--------------------------------------------------------------------------------
/src/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import {useEffect} from "react"
2 | import { useNavigate } from "react-router-dom";
3 | import {Link} from "react-router-dom"
4 | import { currencyFormatter } from "../utlities/currencyFormatter";
5 | import { BsArrowLeft } from "react-icons/bs";
6 | import { useSelector, useDispatch } from "react-redux";
7 | import { removeFromCart,clearCart ,decreaseCart,addToCart,getSubtotal} from "../features/products/cartSlice.js";
8 | import PayButton from "../components/PayButton.jsx";
9 |
10 | const Cart = () => {
11 | const navigate = useNavigate();
12 | const dispatch = useDispatch();
13 | const { cartItems: data ,cartTotalAmount:subtotal} = useSelector((state) => state.cart);
14 |
15 | const productCard = () => {
16 | navigate("/products");
17 | };
18 |
19 | const handleRemove = (product) => {
20 | dispatch(removeFromCart(product));
21 | };
22 |
23 | const handleDecrease=(product)=>{
24 | dispatch(decreaseCart(product))
25 | }
26 |
27 | const handleIncrease=(product)=>{
28 | dispatch(addToCart(product))
29 | }
30 |
31 |
32 | useEffect(() => {
33 | dispatch(getSubtotal())
34 | }, [data,dispatch])
35 |
36 |
37 |
38 | return (
39 |
40 |
41 | {data.length>0? `You've added ${data.length} item${data.length>1? "s":""}`:"Cart is Empty" }
42 |
43 |
44 | {data.length===0 && (
45 | Start 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 |
60 |
61 |

66 |
67 | {product.name}
68 |
74 |
75 |
76 |
{currencyFormatter(product.price)}
77 |
78 |
83 |
86 |
91 |
92 |
93 | {" "}
94 | {currencyFormatter(product.price * product.cartQuantity)}
95 |
96 |
97 | ))}
98 |
99 |
100 |
106 |
107 |
108 | Subtotal
109 | {currencyFormatter(subtotal)}
110 |
111 |
112 | Taxes and shipping costs are calculated at the checkout
113 |
114 |
115 | {/* {" "} */}
118 |
125 |
126 |
127 | >)
128 | }
129 |
130 |
131 | );
132 | };
133 |
134 | export default Cart;
135 |
--------------------------------------------------------------------------------