├── public
├── _redirects
├── robots.txt
├── netlify.toml
├── favicon.ico
├── logo192.png
├── logo512.png
├── images
│ ├── alexa.jpg
│ ├── mouse.jpg
│ ├── phone.jpg
│ ├── airpods.jpg
│ ├── camera.jpg
│ └── playstation.jpg
├── manifest.json
└── index.html
├── src
├── components
│ ├── Stripe.module.css
│ ├── Layout
│ │ ├── Footer.js
│ │ └── Header.js
│ ├── OrderSuccess.js
│ ├── StripeElement.js
│ ├── Account.js
│ ├── Products.js
│ ├── Product.js
│ ├── OrderHistory.js
│ ├── EditProfile.js
│ ├── Checkout.js
│ ├── Cart.js
│ ├── Signin.js
│ ├── Order.js
│ ├── Stripe.js
│ └── Payment.js
├── store
│ ├── index.js
│ ├── OrderSlice.js
│ ├── AuthSlice.js
│ └── CartSlice.js
├── index.css
├── index.js
└── App.js
├── netlify.toml
├── .env
├── .gitignore
├── package.json
└── README.md
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* index.html 200
--------------------------------------------------------------------------------
/src/components/Stripe.module.css:
--------------------------------------------------------------------------------
1 | /* Variables */
2 |
3 |
4 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/public/images/alexa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/alexa.jpg
--------------------------------------------------------------------------------
/public/images/mouse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/mouse.jpg
--------------------------------------------------------------------------------
/public/images/phone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/phone.jpg
--------------------------------------------------------------------------------
/public/images/airpods.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/airpods.jpg
--------------------------------------------------------------------------------
/public/images/camera.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/camera.jpg
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_KEY = pk_test_51HABtrHi9evVBkpvXCh8oNUugE2YJrDnsfjYxNyvBltxWGKDPzIPfjPiEjNxVIKewmgBMfqpmnawlWCmqzClxPQ900nRz9V8uQ
--------------------------------------------------------------------------------
/public/images/playstation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pulkit3234/Ecommerce-Store-Frontend/HEAD/public/images/playstation.jpg
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import CartSlice from './CartSlice';
3 | import authSlice from './AuthSlice';
4 | import orderSlice from './OrderSlice';
5 |
6 | const store = configureStore({
7 | reducer: { cart: CartSlice.reducer, auth : authSlice.reducer, order : orderSlice.reducer },
8 | });
9 |
10 | export default store;
11 |
--------------------------------------------------------------------------------
/.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 | /build
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/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import 'bootstrap/dist/css/bootstrap.min.css';
5 | import App from './App';
6 | import { BrowserRouter } from 'react-router-dom';
7 | import { Provider } from 'react-redux';
8 | import store from './store/index';
9 |
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 | ,
17 | document.getElementById('root')
18 | );
19 |
20 |
21 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Layout/Footer.js:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 | <>
4 |
23 | Made with by Pulkit Sharma
24 |
25 | >
26 | );
27 | };
28 |
29 | export default Footer;
30 |
--------------------------------------------------------------------------------
/src/components/OrderSuccess.js:
--------------------------------------------------------------------------------
1 | const OrderSuccess = () => {
2 | return (
3 | <>
4 |
5 |
9 |
10 |
11 | Order has been Successfully placed! You will recieve your order shortly{' '}
12 |
13 |
14 |
15 | You can see your Order from the Order
16 | history in My Account!
17 |
18 | >
19 | );
20 | };
21 |
22 | export default OrderSuccess;
23 |
--------------------------------------------------------------------------------
/src/components/StripeElement.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CardElement } from '@stripe/react-stripe-js';
3 | import './CardSectionStyles.css';
4 |
5 | const CARD_ELEMENT_OPTIONS = {
6 | style: {
7 | base: {
8 | color: '#32325d',
9 | fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
10 | fontSmoothing: 'antialiased',
11 | fontSize: '16px',
12 | '::placeholder': {
13 | color: '#aab7c4',
14 | },
15 |
16 |
17 | },
18 | invalid: {
19 | color: '#fa755a',
20 | iconColor: '#fa755a',
21 | },
22 | },
23 | };
24 |
25 | function CardSection() {
26 | return (
27 |
28 | Card details
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default CardSection;
36 |
--------------------------------------------------------------------------------
/src/store/OrderSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const orderSlice = createSlice({
4 | name: 'order',
5 | initialState: {
6 | pastOrders: [],
7 | currentOrder: JSON.parse(localStorage.getItem('order')) || {},
8 | paymentMethod: JSON.parse(localStorage.getItem('order'))?.paymentMethod || '',
9 | address: JSON.parse(localStorage.getItem('order'))?.shippingAddress || {},
10 | },
11 | reducers: {
12 | currentOrderHandler(state, action) {
13 | console.log(action.payload);
14 | localStorage.setItem('order', JSON.stringify(action.payload.order));
15 | state.currentOrder = action.payload.order;
16 | },
17 | pastOrdersHandler(state, action) {},
18 | orderSuccess(state, action) {},
19 | orderFailed(state, action) {},
20 | },
21 | });
22 |
23 | /*export const order = () => {
24 | return (dispatch) => {
25 |
26 |
27 | };
28 | } */
29 |
30 | export const orderActions = orderSlice.actions;
31 | export default orderSlice;
32 |
--------------------------------------------------------------------------------
/src/store/AuthSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const authSlice = createSlice({
4 | name: 'auth',
5 | initialState: {
6 | isAuth: JSON.parse(localStorage.getItem('authState'))?.isAuth
7 | ? JSON.parse(localStorage.getItem('authState')).isAuth
8 | : false,
9 | token: JSON.parse(localStorage.getItem('authState'))?.token
10 | ? JSON.parse(localStorage.getItem('authState')).token
11 | : '',
12 | name: JSON.parse(localStorage.getItem('authState'))?.name
13 | ? JSON.parse(localStorage.getItem('authState')).name
14 | : '',
15 | email: JSON.parse(localStorage.getItem('authState'))?.email
16 | ? JSON.parse(localStorage.getItem('authState')).email
17 | : '',
18 | },
19 | reducers: {
20 | signin(state, action) {
21 |
22 | localStorage.setItem('authState', JSON.stringify(action.payload)); // authState object has token and isAuth property.
23 | state.isAuth = true;
24 | },
25 | signout(state, action) {
26 | //localStorage.removeItem('token');
27 | localStorage.clear();
28 | state.isAuth = false;
29 | },
30 | },
31 | });
32 |
33 | export const authActions = authSlice.actions;
34 | export default authSlice;
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.5.1",
7 | "@stripe/react-stripe-js": "^1.4.0",
8 | "@stripe/stripe-js": "^1.14.0",
9 | "@testing-library/jest-dom": "^5.11.4",
10 | "@testing-library/react": "^11.1.0",
11 | "@testing-library/user-event": "^12.1.10",
12 | "axios": "^0.21.1",
13 | "bootstrap": "^4.6.0",
14 | "react": "^17.0.2",
15 | "react-bootstrap": "^1.5.2",
16 | "react-dom": "^17.0.2",
17 | "react-redux": "^7.2.4",
18 | "react-router-dom": "^5.2.0",
19 | "react-scripts": "4.0.3",
20 | "react-stripe-checkout": "^2.6.3",
21 | "styled-components": "^5.3.0",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import Header from './components/Layout/Header';
2 | import React from 'react';
3 | import Products from './components/Products';
4 | import { Switch, Route } from 'react-router-dom';
5 | import Product from './components/Product';
6 | import Cart from './components/Cart';
7 | import Checkout from './components/Checkout';
8 | import Signin from './components/Signin';
9 | import Account from './components/Account';
10 | import Footer from './components/Layout/Footer';
11 | import Order from './components/Order'
12 | import Payment from './components/Payment';
13 | import OrderSuccess from './components/OrderSuccess';
14 | import OrderHistory from './components/OrderHistory';
15 | import EditProfile from './components/EditProfile';
16 |
17 | function App() {
18 | return (
19 | <>
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | >
60 | );
61 | }
62 |
63 | export default App;
64 | //
65 |
66 | /*
67 |
68 | */
69 |
70 |
71 | /* */
--------------------------------------------------------------------------------
/src/components/Account.js:
--------------------------------------------------------------------------------
1 | import { Container, Card, Button, } from 'react-bootstrap';
2 | import { useSelector } from 'react-redux';
3 | import { NavLink, Link } from 'react-router-dom';
4 |
5 | const Account = () => {
6 | const { wishlist } = useSelector((state) => state.cart);
7 | const { name, email } = useSelector((state) => state.auth);
8 |
9 | console.log('account', name, email);
10 | console.log(name, email);
11 |
12 | const wishlistItems = wishlist.map((item) => {
13 | return (
14 |
15 |
16 | {item.name}
17 | {item.price}
18 |
19 |
20 | );
21 | });
22 | return (
23 | <>
24 |
25 |
26 |
27 |
28 | Account Information
29 |
30 |
31 |
32 | Name - {JSON.parse(localStorage.getItem('authState')).name}
33 |
34 |
35 | Email - {JSON.parse(localStorage.getItem('authState')).email}
36 |
37 |
38 |
39 |
40 | Edit Information
41 |
42 |
43 |
44 |
45 |
46 | Order History
47 |
48 |
49 |
50 |
51 |
52 | >
53 | );
54 | };
55 |
56 | export default Account;
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SHOP ALL ECOMMERCE APP
2 | 
3 |
4 | ## Live Website - https://shop-all.netlify.app/
5 | ## About/Description
6 | This app is a fully functional ecommerce app and users can view single product, increase/decrease the quantity of product and then move to checkout and pay using stripe or paypal whichever they want. After placing the order, the order can be viewed from the order history. Users can also edit the user info from the my account section in the app.
7 |
8 | ## Functionalities
9 | * Login/Signup using JWT token
10 | * Paypal/Stripe Payment Options
11 | * Order History
12 | * Edit User Information
13 | * Protected Routes using backend
14 |
15 | ## Tech Used:
16 | ### Frontend
17 | * HTML
18 | * CSS
19 | * JAVASCRIPT
20 | * REACT BOOTSTRAP
21 | * REACT
22 | * REDUX
23 | * PAYPAL SDK
24 | * STRIPE NPM MODULE
25 |
26 | ### Backend
27 | * NODEJS/EXPRESS
28 | * MONGODB
29 | * JWT
30 | * BCRYPTJS
31 |
32 | ### Icons - Fonts Awesome
33 |
34 | ## Outcome/Learning
35 | I have learnt a lot during the implementation of this project, first of all this project is a medium to large scale commercial project so a lot of planning was involved which I had to do. Then I learnt how the redux works flawlessly in big apps where multiple reducers are required, in this app login, cart functionality, order and payment management all is done with the great redux toolkit, and using redux makes the state management and accessibility within the components very much easy. Also I learnt how to implement the login functionality using JWT token and bcrypt packages which was a bonus point. On the backend I learnt how to plan schema building in mongoDB of these type of commercial projects in which there are many to many relationships, for example a user can have many orders etc, also efficiently managing all this many to many data is what I learnt while building this project.
36 |
37 | ## NPM Commands
38 | * `npm i` - installs all the dependencies
39 | * `npm start` - starts the frontend development server on your local machine.
40 |
41 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
31 | React App
32 |
33 |
34 |
35 | You need to enable JavaScript to run this app.
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/Products.js:
--------------------------------------------------------------------------------
1 | import { Container, Row, Col, Card, Button } from 'react-bootstrap';
2 | import { useEffect, useState } from 'react';
3 | import axios from 'axios';
4 | import { useHistory } from 'react-router-dom';
5 | import { cartActions } from '../store/CartSlice';
6 | import { useDispatch, useSelector } from 'react-redux';
7 |
8 |
9 | const Products = () => {
10 | const [products, setProducts] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 | const {isAuth} = useSelector(state => state.auth);
13 | const history = useHistory();
14 | const dispatch = useDispatch();
15 | useEffect(() => {
16 | const fetch = async () => {
17 |
18 | try {
19 |
20 | const { data } = await axios.get('https://shoppall.herokuapp.com/products');
21 | console.log(data);
22 | setProducts(data);
23 | setLoading(false);
24 |
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | };
29 |
30 | fetch();
31 |
32 | /*const fetchCart = async () => {
33 | try {
34 |
35 | const { data } = await axios.get('http://localhost:8000/cart');
36 | if (data) {
37 | dispatch(cartActions.cartReset(data));
38 | }
39 | } catch (error) {
40 | console.log(error);
41 | }
42 |
43 | };
44 |
45 | if(isAuth){
46 | fetchCart();
47 |
48 | } */
49 |
50 |
51 | }, []);
52 |
53 | console.log(products);
54 |
55 | const clickHandler = (id) => {
56 | history.push(id);
57 | };
58 |
59 | return (
60 |
61 | {loading ? (
62 | Loading ....
63 | ) : (
64 |
65 | {products?.map((product) => {
66 | return (
67 |
68 |
69 |
70 |
71 | {product.name}
72 |
73 | {`Brand - ${product.brand}`}
74 | {`$${product.price} `}
75 |
76 | {product.numReviews > 1
77 | ? `${product.numReviews} reviews`
78 | : `${product.numReviews} review`}
79 |
80 |
81 | clickHandler(product._id)}>
82 | Know More
83 |
84 |
85 |
86 |
87 | );
88 | })}
89 |
90 | )}
91 |
92 | );
93 | };
94 | export default Products;
95 |
--------------------------------------------------------------------------------
/src/components/Product.js:
--------------------------------------------------------------------------------
1 | import { Row, Col, Container, Image, Button } from 'react-bootstrap';
2 | import { useEffect, useState } from 'react';
3 | import { useParams } from 'react-router-dom';
4 | import axios from 'axios';
5 | import { useSelector, useDispatch } from 'react-redux';
6 | import { cartActions } from '../store/CartSlice';
7 |
8 | const Product = () => {
9 | const { id } = useParams();
10 | const [data, setData] = useState({});
11 | const [loading, setLoading] = useState(true);
12 | const dispatch = useDispatch();
13 | const [btnToggle, setBtnToggle] = useState(false);
14 |
15 | const cart = useSelector((state) => state.cart);
16 | console.log(cart);
17 | console.log(JSON.parse(localStorage.getItem('state')));
18 |
19 | console.log(id);
20 |
21 | const addToCartHandler = () => {
22 | dispatch(cartActions.addItem(data));
23 | setBtnToggle(true);
24 | };
25 |
26 | const removeFromCartHandler = () => {
27 | dispatch(cartActions.removeItem(data));
28 | setBtnToggle(false);
29 | };
30 |
31 | const wishListHandler = (data) => {
32 | dispatch(cartActions.addWishlist(data));
33 | };
34 |
35 | useEffect(() => {
36 | const fetch = async () => {
37 | const { data } = await axios.get(`https://shoppall.herokuapp.com/product/${id}`);
38 | console.log(data);
39 |
40 | setData(data);
41 | setLoading(false);
42 | };
43 |
44 | fetch();
45 | }, []);
46 |
47 | return (
48 | <>
49 | {loading ? (
50 | Loading.....
51 | ) : (
52 |
53 |
54 |
55 |
61 |
62 |
63 | {data.brand}
64 | {data.description}
65 |
66 | {data.countInStock > 0 ? 'In Stock' : 'Out of Stock'}{' '}
67 |
68 | {`Price - $ ${data.price}`}
69 | {btnToggle ? (
70 |
75 | Remove Item
76 |
77 | ) : (
78 |
84 | Add To Cart
85 |
86 | )}
87 |
88 |
89 |
90 |
91 |
92 | )}
93 | >
94 | );
95 | };
96 |
97 | export default Product;
98 |
--------------------------------------------------------------------------------
/src/components/Layout/Header.js:
--------------------------------------------------------------------------------
1 | import { Navbar, NavDropdown, Nav } from 'react-bootstrap';
2 | import { NavLink, Link } from 'react-router-dom';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import { authActions } from '../../store/AuthSlice';
5 | import { useHistory } from 'react-router-dom';
6 | import { cartActions } from '../../store/CartSlice';
7 |
8 | const Header = () => {
9 | const { totalItems } = useSelector((state) => state.cart);
10 | const { isAuth, token } = useSelector((state) => state.auth);
11 | const dispatch = useDispatch();
12 | const history = useHistory();
13 | console.log(isAuth);
14 | //console.log(cart);
15 |
16 | const signOutHandler = () => {
17 | dispatch(cartActions.clearCart());
18 | dispatch(authActions.signout());
19 |
20 | history.push('/signin');
21 | };
22 |
23 | return (
24 | <>
25 |
26 |
27 |
28 |
29 |
30 |
31 | Shop All
32 |
33 |
34 |
35 |
36 |
37 |
38 | Home
39 |
40 |
41 | {isAuth ? (
42 |
43 | Sign Out
44 |
45 | ) : (
46 |
47 |
48 | SignIn
49 |
50 |
51 | )}
52 |
53 | {isAuth && (
54 |
55 |
56 | My Account
57 |
58 |
59 | )}
60 |
61 |
62 |
63 | {totalItems !== 0 && (
64 |
76 | {totalItems}
77 |
78 | )}
79 | Cart
80 |
81 |
82 |
83 |
84 |
85 |
86 | >
87 | );
88 | };
89 |
90 | export default Header;
91 |
--------------------------------------------------------------------------------
/src/store/CartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | //const localStoarage = JSON.parse(localStorage.getItem('state'));
4 | const stateCartItems = JSON.parse(localStorage.getItem('state'))?.cartItems
5 | ? JSON.parse(localStorage.getItem('state')).cartItems
6 | : [];
7 | const stateTotalPrice = JSON.parse(localStorage.getItem('state'))?.totalPrice
8 | ? JSON.parse(localStorage.getItem('state')).totalPrice
9 | : 0;
10 | const stateTotalItems = JSON.parse(localStorage.getItem('state'))?.totalItems
11 | ? JSON.parse(localStorage.getItem('state')).totalItems
12 | : 0;
13 | const stateWishlist = JSON.parse(localStorage.getItem('state'))?.wishlist
14 | ? JSON.parse(localStorage.getItem('state')).wishlist
15 | : [];
16 |
17 | const cartSlice = createSlice({
18 | name: 'cart',
19 | initialState: {
20 | cartItems: stateCartItems,
21 | totalPrice: stateTotalPrice,
22 | totalItems: stateTotalItems,
23 | wishlist: stateWishlist,
24 | },
25 | reducers: {
26 | addItem(state, action) {
27 | console.log(action.payload.price);
28 | console.log(state);
29 |
30 | if (state.cartItems.findIndex((item) => item._id === action.payload._id) !== -1) {
31 | console.log('hello');
32 | const index = state.cartItems.findIndex((item) => item._id === action.payload._id);
33 | if (state.cartItems[index].countInStock === 0) {
34 | return;
35 | }
36 |
37 | console.log(index);
38 | state.cartItems[index].countInStock = state.cartItems[index].countInStock - 1;
39 | state.cartItems[index].qty++;
40 | console.log(state.cartItems);
41 | } else {
42 | action.payload['qty'] = 1;
43 | console.log(action.payload);
44 |
45 | state.cartItems.push(action.payload);
46 | console.log(action.payload.price);
47 | }
48 | console.log(state.totalPrice);
49 | state.totalPrice = Number((state.totalPrice + action.payload.price).toFixed(2));
50 | // Number(state.totalPrice).toFixed(2);
51 |
52 | state.totalItems = state.totalItems + 1;
53 | localStorage.setItem('state', JSON.stringify(state));
54 | },
55 |
56 | removeItem(state, action) {
57 | const result = state.cartItems.find((item) => item._id === action.payload._id);
58 | const resultIndex = state.cartItems.findIndex((item) => item._id === action.payload._id);
59 | if (result.qty === 1) {
60 | state.cartItems[resultIndex].stockInCount--;
61 |
62 | //result.stockInCount++;
63 | state.cartItems = state.cartItems.filter((item) => item._id !== action.payload._id);
64 |
65 | //console.log(result);
66 | } else {
67 | result.qty--;
68 | result.stockInCount++;
69 | }
70 |
71 | state.totalItems--;
72 | state.totalPrice = Number((state.totalPrice - result.price).toFixed(2));
73 | localStorage.setItem('state', JSON.stringify(state.cartItems));
74 | console.log(JSON.parse(localStorage.getItem('state')).totalItems);
75 | },
76 | clearCart(state, action) {
77 | state.cartItems = [];
78 | state.totalPrice = 0;
79 | state.totalItems = 0;
80 | localStorage.setItem('state', JSON.stringify(state));
81 | },
82 | cartReset(state, action) {
83 | const { cartItems, totalItems, totalPrice } = JSON.parse(localStorage.getItem('state'));
84 | state.cartItems = cartItems;
85 | state.totalItems = totalItems;
86 | state.totalPrice = totalPrice;
87 | },
88 | addWishlist(state, action) {
89 | state.wishlist.push(action.payload);
90 | },
91 | },
92 | });
93 |
94 | export const cartActions = cartSlice.actions;
95 | export default cartSlice;
96 |
--------------------------------------------------------------------------------
/src/components/OrderHistory.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { Card, Button, Row, Accordion, Col } from 'react-bootstrap';
3 | import { useEffect, useState } from 'react';
4 |
5 | const SuccessfulOrders = () => {
6 | const [orders, setOrders] = useState([]);
7 | const [noOrder, setNoOrder] = useState(false);
8 |
9 | useEffect(() => {
10 | const fetch = async () => {
11 | try {
12 | const { data } = await axios.get('https://shoppall.herokuapp.com/paid', {
13 | headers: {
14 | Authorization: `Bearer ${JSON.parse(localStorage.getItem('authState')).token}`,
15 | },
16 | });
17 | console.log(data);
18 | if ((data.message = 'No orders placed')) {
19 | setNoOrder(true);
20 | }
21 | setOrders(data.paidOrders);
22 |
23 | //setOrders(data);
24 |
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | };
29 |
30 | fetch();
31 | }, []);
32 |
33 | const paidOrders = orders.map((order) => {
34 | console.log(order);
35 | return (
36 |
37 |
38 |
39 |
40 |
41 | {`Order Id - ${order._id}`}
42 |
43 | Total Price - ${order.totalPrice}
44 |
45 |
46 | Total Items -
47 | {order.totalItems}
48 |
49 |
50 | Order Status -
51 | {'Paid'}
52 |
53 |
54 | Payment Method -
55 | {order.paymentMethod.toUpperCase(0)}
56 |
57 |
58 | Order Date -
59 | {new Date(order.createdAt).toDateString()}
60 |
61 |
62 |
63 |
64 |
65 |
66 | Order Items
67 |
68 |
69 |
70 |
71 |
72 | {order.cartItems.map((item) => {
73 | return (
74 |
75 |
76 |
77 | {item.name}
78 | Quantity - {item.qty}
79 | Price - ${item.price}
80 |
81 |
82 | );
83 | })}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | });
94 | console.log(paidOrders);
95 | return (
96 | <>
97 | Orders Placed
98 | {orders.length === 0 ?
99 | <>{noOrder ? No Order Found : Loading.... }>
100 |
101 | : (
102 | <>
103 |
104 |
105 |
113 | {paidOrders}
114 |
115 | >
116 | )}
117 | >
118 | );
119 | };
120 |
121 | export default SuccessfulOrders;
122 |
--------------------------------------------------------------------------------
/src/components/EditProfile.js:
--------------------------------------------------------------------------------
1 | import { Form, Button, Row } from 'react-bootstrap';
2 | import { useState, useRef, useEffect } from 'react';
3 | import axios from 'axios';
4 | import { useDispatch } from 'react-redux';
5 | import { useHistory } from 'react-router-dom';
6 |
7 | const EditProfile = () => {
8 | const emailRef = useRef();
9 | const nameRef = useRef();
10 | const passwordRef = useRef();
11 | const [loading, setLoading] = useState(false);
12 | const dispatch = useDispatch();
13 | const history = useHistory();
14 |
15 | useEffect(() => {
16 | emailRef.current.value = JSON.parse(localStorage.getItem('authState')).name;
17 | nameRef.current.value = JSON.parse(localStorage.getItem('authState')).email;
18 | }, []);
19 |
20 | const formSubmitHandler = async (e) => {
21 | e.preventDefault();
22 | setLoading(true);
23 |
24 | const name = nameRef.current.value;
25 | const email = emailRef.current.value;
26 | const password = passwordRef.current.value;
27 |
28 | console.log(email, name, password);
29 |
30 |
31 | try {
32 | const { data } = await axios.post(
33 | 'https://shoppall.herokuapp.com/profile/edit',
34 | {
35 | name,
36 | email,
37 | password,
38 | },
39 | {
40 | headers: {
41 | Authorization: `Bearer ${JSON.parse(localStorage.getItem('authState')).token}`,
42 | },
43 | }
44 | );
45 | console.log(data);
46 | //At the backend the data is updated, now update data in to local storage also.
47 |
48 | const token = JSON.parse(localStorage.getItem('authState')).token;
49 | const isAuth = JSON.parse(localStorage.getItem('authState')).token;
50 | const newAuthState = {
51 | name: data.updatedName,
52 | email: data.updatedEmail,
53 | token,
54 | isAuth,
55 | };
56 |
57 | localStorage.setItem('authState', JSON.stringify(newAuthState));
58 | console.log(JSON.parse(localStorage.getItem('authState')));
59 |
60 | history.push('/customer/account');
61 |
62 | setLoading(false);
63 | } catch (error) {
64 | console.log(error);
65 | }
66 | };
67 | return (
68 | <>
69 | Edit Profile Details
70 | {loading ? (
71 | Updating Details.....
72 | ) : (
73 |
83 |
85 | Email address
86 |
87 | We'll never share your email with anyone else.
88 |
89 |
90 | Name
91 |
92 | We'll never share your email with anyone else.
93 |
94 |
95 |
96 | Password
97 |
98 |
99 | If you want to change the password change it otherwise leave it blank.
100 |
101 |
102 |
103 |
104 | Update Profile
105 |
106 |
107 |
108 | )}
109 | >
110 | );
111 | };
112 |
113 | export default EditProfile;
114 |
--------------------------------------------------------------------------------
/src/components/Checkout.js:
--------------------------------------------------------------------------------
1 | import { Form, Button, Col, Container } from 'react-bootstrap';
2 | import { useHistory } from 'react-router-dom';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import Signin from './Signin';
5 | import axios from 'axios';
6 | import { useState, useRef, useEffect } from 'react';
7 | import { orderActions, order } from '../store/OrderSlice';
8 |
9 | const Checkout = () => {
10 | const history = useHistory();
11 | const { isAuth, token } = useSelector((state) => state.auth);
12 | const cartState = useSelector((state) => state.cart);
13 | const dispatch = useDispatch();
14 |
15 | console.log(JSON.parse(localStorage.getItem('authState'))?.token)
16 |
17 | const addressRef = useRef();
18 | const countryRef = useRef();
19 | const cityRef = useRef();
20 | const postalCodeRef = useRef();
21 |
22 | useEffect(() => {
23 | if (JSON.parse(localStorage.getItem('shippingAddress'))) {
24 | const { address, postalCode, country, city } = JSON.parse(localStorage.getItem('shippingAddress'));
25 |
26 | addressRef.current.value = address;
27 | cityRef.current.value = city;
28 | countryRef.current.value = country;
29 | postalCodeRef.current.value = postalCode;
30 | }
31 | }, []);
32 |
33 | const checkoutHandler = (e) => {
34 | e.preventDefault();
35 | console.log('click');
36 |
37 | if (
38 | !addressRef.current?.value ||
39 | !countryRef.current?.value ||
40 | !cityRef.current?.value ||
41 | !postalCodeRef.current?.value
42 | ) {
43 | console.log('clicked');
44 | return;
45 | }
46 |
47 | localStorage.setItem(
48 | 'shippingAddress',
49 | JSON.stringify({
50 | address: addressRef.current.value,
51 | country: countryRef.current.value,
52 | city: cityRef.current.value,
53 | postalCode: postalCodeRef.current.value,
54 | })
55 | );
56 |
57 | history.push('/cart/checkout/order');
58 |
59 |
60 | };
61 |
62 | return (
63 | <>
64 | {isAuth ? (
65 |
74 |
75 |
77 | Address
78 |
79 |
80 |
81 |
82 |
83 | Country
84 |
85 |
86 |
87 |
88 | City
89 |
90 |
91 |
92 |
93 | Postal Code
94 |
95 |
96 |
97 |
98 |
99 |
105 | Continue To Order
106 |
107 |
108 |
109 |
110 |
111 | ) : (
112 |
113 |
114 |
Please Sign In To Proceed!
115 |
116 |
117 |
118 |
119 |
120 | )}
121 | >
122 | );
123 | };
124 |
125 | export default Checkout;
126 |
--------------------------------------------------------------------------------
/src/components/Cart.js:
--------------------------------------------------------------------------------
1 | import { Col, Row, ListGroup, Container, Card, Button, Image } from 'react-bootstrap';
2 | import { Link, useHistory } from 'react-router-dom';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import { cartActions } from '../store/CartSlice';
5 | import { useEffect } from 'react';
6 | import axios from 'axios';
7 | import { authActions } from '../store/AuthSlice';
8 |
9 | const Cart = () => {
10 | let { cartItems, totalItems, totalPrice } = useSelector((state) => state.cart);
11 | console.log(JSON.parse(localStorage.getItem('state')));
12 |
13 | console.log(cartItems);
14 | const { isAuth } = useSelector((state) => state.auth);
15 | console.log(isAuth);
16 |
17 | console.log(totalPrice);
18 | const dispatch = useDispatch();
19 |
20 | const history = useHistory();
21 |
22 | const clearCartHandler = () => {
23 | dispatch(cartActions.clearCart({ cartItems: [], totalItems: 0, totalPrice: 0 }));
24 | };
25 |
26 | const incrementHandler = (item) => {
27 | dispatch(cartActions.addItem(item));
28 | };
29 |
30 | const decrementHandler = (item) => {
31 | dispatch(cartActions.removeItem(item));
32 | };
33 | const items = cartItems.map((item) => {
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {item.name}
45 |
53 |
54 | Price - ${item.price}
55 |
56 |
66 |
67 | decrementHandler(item)}
71 | >
72 |
73 |
{item.qty}
74 |
75 | incrementHandler(item)}
79 | >
80 |
81 |
82 |
83 |
84 |
85 |
86 | );
87 | });
88 | return (
89 | <>
90 |
91 |
92 | My Cart {`(${totalItems} Items)`}
93 |
94 |
95 |
96 |
97 |
98 | {items}
99 |
100 |
101 |
102 |
103 |
104 | Total Amount - ${totalPrice}
105 |
106 |
107 | history.push('/cart/checkout')}
111 | disabled={cartItems.length === 0}
112 | >
113 | Proceed To Checkout
114 |
115 |
121 | Clear Cart
122 |
123 |
124 |
125 |
126 |
127 |
128 | >
129 | );
130 | };
131 |
132 | export default Cart;
133 |
--------------------------------------------------------------------------------
/src/components/Signin.js:
--------------------------------------------------------------------------------
1 | import { Form, Button } from 'react-bootstrap';
2 | import { useState, useRef } from 'react';
3 | import axios from 'axios';
4 | import { authActions } from '../store/AuthSlice';
5 | import { useDispatch } from 'react-redux';
6 | import { useHistory } from 'react-router-dom';
7 | import Cart from './Cart';
8 |
9 | const SignIn = () => {
10 | const [toggle, setToggle] = useState(false);
11 | const [email, setEmail] = useState('');
12 | const [name, setName] = useState('');
13 | const [password, setPassword] = useState('');
14 | const emailRef = useRef();
15 | const dispatch = useDispatch();
16 | const history = useHistory();
17 |
18 | const onSubmitHandler = async (e) => {
19 | e.preventDefault();
20 |
21 | if (toggle) {
22 | const { data } = await axios.post('https://shoppall.herokuapp.com/signup', {
23 | name,
24 | email,
25 | password,
26 | });
27 | if ((data.message = 'User registered')) {
28 | setEmail('');
29 | setPassword('');
30 | setToggle(!toggle);
31 | }
32 | } else {
33 | const { data } = await axios.post('https://shoppall.herokuapp.com/signin', {
34 | email,
35 | password,
36 | });
37 | console.log(data);
38 |
39 | if (data.token) {
40 | dispatch(authActions.signin({ token: data.token, isAuth: true, name: data.name, email: data.email }));
41 | history.push('/');
42 | }
43 |
44 |
45 | }
46 |
47 |
48 |
49 | };
50 |
51 | const signin = (
52 |
53 |
55 |
56 | Email address
57 | setEmail(e.target.value)}
62 | style={{ width: '40%' }}
63 | />
64 |
65 |
66 |
67 |
68 |
69 | Password
70 | setPassword(e.target.value)}
75 | style={{ width: '40%' }}
76 | />
77 |
78 |
79 |
80 |
81 | Sign In
82 |
83 |
84 | setToggle(true)} style={{ cursor: 'pointer' }}>
85 | New User? Sign Up Now
86 |
87 |
88 |
89 |
90 | );
91 |
92 | const signup = (
93 |
94 |
96 |
97 | Name
98 | setName(e.target.value)}
103 | style={{ width: '40%' }}
104 | />
105 |
106 |
107 | We'll never share your name with anyone else.
108 |
109 |
110 |
111 | Email address
112 | setEmail(e.target.value)}
117 | style={{ width: '40%' }}
118 | />
119 |
120 |
121 |
We'll never share your email with anyone else.
122 |
123 |
124 |
125 | Password
126 | setPassword(e.target.value)}
131 | style={{ width: '40%' }}
132 | />
133 |
134 |
135 |
136 |
137 | Sign Up
138 |
139 |
140 | setToggle(false)} style={{ cursor: 'pointer' }}>
141 | Already Registered ? Sign In Now
142 |
143 |
144 |
145 |
146 | );
147 | return <>{toggle ? signup : signin}>;
148 | };
149 |
150 | export default SignIn;
151 |
--------------------------------------------------------------------------------
/src/components/Order.js:
--------------------------------------------------------------------------------
1 | import { Col, Row, Container, ListGroup, Card, Button, InputGroup, FormControl } from 'react-bootstrap';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { useRef, useState } from 'react';
4 | import { orderActions } from '../store/OrderSlice';
5 | import axios from 'axios';
6 | import { useHistory } from 'react-router-dom';
7 |
8 | const Order = () => {
9 | const { isAuth } = useSelector((state) => state.auth);
10 | const { currentOrder: order } = useSelector((state) => state.order);
11 | const state = useSelector((state) => state.order);
12 | const cartState = useSelector((state) => state.cart);
13 | const dispatch = useDispatch();
14 | const history = useHistory();
15 | const [loading, setLoading] = useState(false);
16 | const [payment, setPayment] = useState('');
17 |
18 | const { address, city, postalCode, country } = JSON.parse(localStorage.getItem('shippingAddress'));
19 |
20 | const placeOrderHandler = () => {
21 | console.log(payment);
22 | setLoading(true);
23 | const sendCartData = async () => {
24 | try {
25 | const { data } = await axios.post(
26 | 'https://shoppall.herokuapp.com/cart',
27 | {
28 | ...cartState,
29 | shippingAddress: {
30 | ...JSON.parse(localStorage.getItem('shippingAddress')),
31 | },
32 | paymentMethod: payment,
33 | },
34 | {
35 | headers: {
36 | Authorization: `Bearer ${JSON.parse(localStorage.getItem('authState')).token}`,
37 | },
38 | }
39 | );
40 |
41 | console.log(data);
42 | dispatch(orderActions.currentOrderHandler(data));
43 | setLoading(false);
44 | history.push(`/order/${JSON.parse(localStorage.getItem('order'))._id}`);
45 | } catch (error) {
46 | console.log(error.message);
47 | }
48 | };
49 |
50 | if (payment) {
51 | sendCartData();
52 | }
53 | };
54 |
55 | console.log(JSON.parse(localStorage.getItem(state)));
56 | const orderItems = JSON.parse(localStorage.getItem('state'))?.cartItems.map((item) => {
57 | return (
58 |
62 |
63 |
64 |
65 |
66 |
{item.name}
67 |
${item.price}
68 |
69 |
72 |
73 | );
74 | });
75 |
76 | return loading ? (
77 | Loading......
78 | ) : (
79 | <>
80 | {isAuth ? (
81 | <>
82 | {state ? (
83 | <>
84 |
85 |
86 |
87 |
88 | Shipping Details
89 |
90 |
91 | Address - {address}
92 |
93 |
94 | Country - {country}
95 |
96 |
97 | City - {city}
98 |
99 |
100 | Postal Code - {postalCode}
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Order Summary
112 |
113 |
114 |
115 | Total Items - {JSON.parse(localStorage.getItem('state')).totalItems}
116 |
117 |
118 | Total Price - $
119 | {JSON.parse(localStorage.getItem('state')).totalPrice}
120 |
121 |
122 |
123 | setPayment(e.target.value)}
130 | />
131 |
132 | Stripe
133 |
134 |
135 |
136 | setPayment(e.target.value)}
143 | />
144 |
145 | Paypal
146 |
147 |
148 |
149 |
155 | Select Payment Method
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | Order Items -
165 |
166 | {orderItems}
167 |
168 |
169 |
170 |
171 | >
172 | ) : (
173 | Loading
174 | )}
175 | >
176 | ) : (
177 | Please Sign In to Place Order!
178 | )}
179 | >
180 | );
181 | };
182 |
183 | export default Order;
184 |
--------------------------------------------------------------------------------
/src/components/Stripe.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { loadStripe } from '@stripe/stripe-js';
3 | import styled from 'styled-components';
4 | import { CardElement, useStripe, Elements, useElements } from '@stripe/react-stripe-js';
5 | import axios from 'axios';
6 |
7 | const promise = loadStripe(process.env.REACT_APP_KEY);
8 |
9 | const CheckoutForm = ({ data: value, paymentSuccess }) => {
10 | const [processing, setProcessing] = useState('');
11 | const [clientSecret, setClientSecret] = useState('');
12 | const stripe = useStripe();
13 | const elements = useElements();
14 |
15 | const cardStyle = {
16 | style: {
17 | base: {
18 | color: '#32325d',
19 | fontFamily: 'Arial, sans-serif',
20 | fontSmoothing: 'antialiased',
21 | fontSize: '16px',
22 | '::placeholder': {
23 | color: '#32325d',
24 | },
25 | },
26 | invalid: {
27 | color: '#fa755a',
28 | iconColor: '#fa755a',
29 | },
30 | },
31 | };
32 |
33 | console.log(value);
34 | const createPaymentIntent = async () => {
35 | try {
36 | const { data } = await axios.post('https://shoppall.herokuapp.com/payment', { value });
37 | console.log(data);
38 | setClientSecret(data.clientSecret);
39 | } catch (error) {
40 | console.log(error);
41 | }
42 | };
43 |
44 | useEffect(() => {
45 | createPaymentIntent();
46 | }, []);
47 |
48 | const handleSubmit = async (e) => {
49 | e.preventDefault();
50 | setProcessing(true);
51 | console.log('clicked');
52 | console.log(clientSecret);
53 |
54 | try {
55 | const payload = await stripe.confirmCardPayment(clientSecret, {
56 | payment_method: {
57 | card: elements.getElement(CardElement),
58 | },
59 | });
60 |
61 | console.log(payload);
62 |
63 | if (payload.error) {
64 | console.log('failed');
65 | setProcessing(false);
66 | console.log(payload.error);
67 | } else {
68 | console.log('success');
69 | setProcessing(false);
70 | paymentSuccess();
71 | }
72 | } catch (error) {
73 | console.log(error);
74 | }
75 | };
76 |
77 | return (
78 | <>
79 |
80 |
81 | Test Card Number - 4242 4242 4242 4242 MM/YY - 11/33
82 |
83 |
84 | CVC - 111 ZIP - 11111
85 |
86 |
87 |
94 |
95 | >
96 | );
97 | };
98 |
99 | const Stripe = ({ data, paymentSuccess }) => {
100 | return (
101 | <>
102 |
103 |
104 |
105 |
106 |
107 | >
108 | );
109 | };
110 |
111 | const Wrapper = styled.section`
112 | form {
113 | width: 100%;
114 | align-self: center;
115 | box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1),
116 | 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
117 | border-radius: 7px;
118 | padding: 40px;
119 | }
120 | input {
121 | border-radius: 6px;
122 | margin-bottom: 6px;
123 | padding: 12px;
124 | border: 1px solid rgba(50, 50, 93, 0.1);
125 | max-height: 44px;
126 | font-size: 16px;
127 | width: 100%;
128 | background: white;
129 | box-sizing: border-box;
130 | }
131 | .result-message {
132 | line-height: 22px;
133 | font-size: 16px;
134 | }
135 | .result-message a {
136 | color: rgb(89, 111, 214);
137 | font-weight: 600;
138 | text-decoration: none;
139 | }
140 | .hidden {
141 | display: none;
142 | }
143 | #card-error {
144 | color: rgb(105, 115, 134);
145 | font-size: 16px;
146 | line-height: 20px;
147 | margin-top: 12px;
148 | text-align: center;
149 | }
150 | #card-element {
151 | border-radius: 4px 4px 0 0;
152 | padding: 12px;
153 | border: 1px solid rgba(50, 50, 93, 0.1);
154 | max-height: 44px;
155 | width: 100%;
156 | background: white;
157 | box-sizing: border-box;
158 | }
159 | #payment-request-button {
160 | margin-bottom: 32px;
161 | }
162 | /* Buttons and links */
163 | button {
164 | background: #5469d4;
165 | font-family: Arial, sans-serif;
166 | color: #ffffff;
167 | border-radius: 0 0 4px 4px;
168 | border: 0;
169 | padding: 12px 16px;
170 | font-size: 16px;
171 | font-weight: 600;
172 | cursor: pointer;
173 | display: block;
174 | transition: all 0.2s ease;
175 | box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
176 | width: 100%;
177 | }
178 | button:hover {
179 | filter: contrast(115%);
180 | }
181 | button:disabled {
182 | opacity: 0.5;
183 | cursor: default;
184 | }
185 | /* spinner/processing state, errors */
186 | .spinner,
187 | .spinner:before,
188 | .spinner:after {
189 | border-radius: 50%;
190 | }
191 | .spinner {
192 | color: #ffffff;
193 | font-size: 22px;
194 | text-indent: -99999px;
195 | margin: 0px auto;
196 | position: relative;
197 | width: 20px;
198 | height: 20px;
199 | box-shadow: inset 0 0 0 2px;
200 | -webkit-transform: translateZ(0);
201 | -ms-transform: translateZ(0);
202 | transform: translateZ(0);
203 | }
204 | .spinner:before,
205 | .spinner:after {
206 | position: absolute;
207 | content: '';
208 | }
209 | .spinner:before {
210 | width: 10.4px;
211 | height: 20.4px;
212 | background: #5469d4;
213 | border-radius: 20.4px 0 0 20.4px;
214 | top: -0.2px;
215 | left: -0.2px;
216 | -webkit-transform-origin: 10.4px 10.2px;
217 | transform-origin: 10.4px 10.2px;
218 | -webkit-animation: loading 2s infinite ease 1.5s;
219 | animation: loading 2s infinite ease 1.5s;
220 | }
221 | .spinner:after {
222 | width: 10.4px;
223 | height: 10.2px;
224 | background: #5469d4;
225 | border-radius: 0 10.2px 10.2px 0;
226 | top: -0.1px;
227 | left: 10.2px;
228 | -webkit-transform-origin: 0px 10.2px;
229 | transform-origin: 0px 10.2px;
230 | -webkit-animation: loading 2s infinite ease;
231 | animation: loading 2s infinite ease;
232 | }
233 | @keyframes loading {
234 | 0% {
235 | -webkit-transform: rotate(0deg);
236 | transform: rotate(0deg);
237 | }
238 | 100% {
239 | -webkit-transform: rotate(360deg);
240 | transform: rotate(360deg);
241 | }
242 | }
243 | @media only screen and (max-width: 600px) {
244 | form {
245 | width: 80vw;
246 | }
247 | }
248 | `;
249 | export default Stripe;
250 |
--------------------------------------------------------------------------------
/src/components/Payment.js:
--------------------------------------------------------------------------------
1 | import { Col, Row, Container, ListGroup, Card, Button } from 'react-bootstrap';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { useState } from 'react';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { useHistory } from 'react-router-dom';
7 | import axios from 'axios';
8 | import { orderActions } from '../store/OrderSlice';
9 | import { cartActions } from '../store/CartSlice';
10 | import Stripe from './Stripe';
11 |
12 | const PayPalButton = window.paypal.Buttons.driver('react', { React, ReactDOM });
13 |
14 | const Payment = () => {
15 | const { isAuth } = useSelector((state) => state.auth);
16 | const [paymentStatus, setPaymentStatus] = useState(false);
17 | const { currentOrder: order } = useSelector((state) => state.order);
18 | console.log(order);
19 | const state = useSelector((state) => state.order);
20 | const history = useHistory();
21 | const dispatch = useDispatch();
22 |
23 | const paymentSuccess = async () => {
24 | try {
25 | const { data } = await axios.post(
26 | 'https://shoppall.herokuapp.com/cart/orderstatus',
27 | {
28 | isPaid: true,
29 | orderId: JSON.parse(localStorage.getItem('order'))?._id,
30 | },
31 | {
32 | headers: {
33 | Authorization: `Bearer ${JSON.parse(localStorage.getItem('authState')).token}`,
34 | },
35 | }
36 | );
37 |
38 | console.log(data);
39 | dispatch(orderActions.currentOrderHandler(data));
40 |
41 | history.push(`/order/${JSON.parse(localStorage.getItem('order'))?._id}/success`);
42 | } catch (error) {
43 | console.log(error.message);
44 | }
45 | //RESET THE CART TOO AFTER PAYMENT.
46 | setPaymentStatus(true);
47 | dispatch(cartActions.clearCart());
48 | };
49 |
50 | console.log(state.currentOrder.cartItems);
51 |
52 | /////////////////Paypal Setup
53 | const createOrder = (data, actions) => {
54 | console.log('order created');
55 | return actions.order.create({
56 | purchase_units: [
57 | {
58 | description: 'Shop All Order',
59 | amount: {
60 | value: order.totalPrice,
61 | currency: 'USD',
62 | },
63 | },
64 | ],
65 | });
66 | };
67 |
68 | const onApprove = async (data, actions) => {
69 | const result = await actions.order.capture();
70 | console.log('approved', result);
71 |
72 | paymentSuccess();
73 | };
74 |
75 | const onError = (error) => {
76 | console.log(error);
77 | history.push(`/order/${order._id}`);
78 | };
79 |
80 | ///////////////
81 | const orderItems = state.currentOrder.cartItems?.map((item) => {
82 | return (
83 |
87 |
88 |
89 |
90 |
91 |
{item.name}
92 |
${item.price}
93 |
94 |
97 |
98 | );
99 | });
100 |
101 | return (
102 | <>
103 | {isAuth ? (
104 | <>
105 |
106 | {`OrderId - ${order?._id}`}
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | Order Summary
122 |
123 |
124 |
125 |
126 |
127 |
128 | Order Items -
129 |
130 | {orderItems}
131 |
132 |
133 |
134 |
135 |
136 |
137 | Total Items - {order?.totalItems}
138 | Total Price - ${order?.totalPrice}
139 |
140 | {!paymentStatus && (
141 |
142 | {JSON.parse(localStorage.getItem('order')).paymentMethod === 'paypal' &&
143 | order.isPaid === false ? (
144 | <>
145 |
createOrder(data, actions)}
147 | onApprove={(data, actions) => onApprove(data, actions)}
148 | onError={(error) => onError(error)}
149 | />
150 |
151 |
152 |
153 | Test Card Details
154 |
155 |
156 | Country
157 | - Australia
158 |
159 |
160 | Card Number
161 | - 4242 4242 4242 4242
162 |
163 |
164 | Expiry Date - 11/33
165 |
166 |
167 | Security Code - 424
168 |
169 |
170 | Name - John Doe
171 |
172 |
173 | Address 1 - 51 Wollombi Street
174 |
175 |
176 |
177 | Town/City
178 | - Broke
179 |
180 |
181 | State - New South Wales
182 |
183 |
184 | Postal Code - 2330
185 |
186 |
187 | Mobile No - 049151-5141
188 |
189 |
190 | Email Address - test@gmail.com
191 |
192 |
193 |
194 |
195 |
196 | >
197 | ) : (
198 | ''
199 | )}
200 |
201 | )}
202 | {!paymentStatus && (
203 |
204 | {JSON.parse(localStorage.getItem('order')).paymentMethod === 'stripe' &&
205 | order.isPaid === false ? (
206 | setPaymentStatus(true)}
208 | data={JSON.parse(localStorage.getItem('order'))}
209 | paymentSuccess={paymentSuccess}
210 | />
211 | ) : (
212 | ''
213 | )}
214 |
215 | )}
216 | {JSON.parse(localStorage.getItem('order')).isPaid && Order - Paid }
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | Shipping Details
225 |
226 |
227 | Address - {order.shippingAddress?.address}
228 |
229 |
230 | Country - {order.shippingAddress?.country}
231 |
232 |
233 | City - {order.shippingAddress?.city}
234 |
235 |
236 | Postal Code - {order.shippingAddress?.postalCode}
237 |
238 |
239 |
240 |
241 |
242 |
243 | >
244 | ) : (
245 | Please Sign In to Place Order!
246 | )}
247 | >
248 | );
249 | };
250 |
251 | export default Payment;
252 |
--------------------------------------------------------------------------------