├── .env ├── .gitignore ├── README.md ├── client ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.js │ ├── GlobalState.js │ ├── api │ ├── CategoriesAPI.js │ ├── ProductsAPI.js │ └── UserAPI.js │ ├── components │ ├── headers │ │ ├── Header.js │ │ ├── header.css │ │ └── icon │ │ │ ├── cart.svg │ │ │ ├── close.svg │ │ │ └── menu.svg │ └── mainpages │ │ ├── Pages.js │ │ ├── auth │ │ ├── Login.js │ │ ├── Register.js │ │ └── login.css │ │ ├── cart │ │ ├── Cart.js │ │ ├── PaypalButton.js │ │ └── cart.css │ │ ├── categories │ │ ├── Categories.js │ │ └── categories.css │ │ ├── createProduct │ │ ├── CreateProduct.js │ │ └── createProduct.css │ │ ├── detailProduct │ │ ├── DetailProduct.js │ │ └── detailProduct.css │ │ ├── history │ │ ├── OrderDetails.js │ │ ├── OrderHistory.js │ │ └── history.css │ │ ├── products │ │ ├── Filters.js │ │ ├── LoadMore.js │ │ ├── Products.js │ │ └── products.css │ │ └── utils │ │ ├── loading │ │ ├── Loading.js │ │ └── loading.css │ │ ├── not_found │ │ └── NotFound.js │ │ └── productItem │ │ ├── BtnRender.js │ │ ├── ProductItem.js │ │ └── productItem.css │ ├── index.css │ └── index.js ├── controllers ├── categoryCtrl.js ├── paymentCtrl.js ├── productCtrl.js └── userCtrl.js ├── middleware ├── auth.js └── authAdmin.js ├── models ├── categoryModel.js ├── paymentModel.js ├── productModel.js └── userModel.js ├── package-lock.json ├── package.json ├── routes ├── categoryRouter.js ├── paymentRouter.js ├── productRouter.js ├── upload.js └── userRouter.js └── server.js /.env: -------------------------------------------------------------------------------- 1 | MONGODB_URL = YOUR_MONGODB_URL 2 | 3 | CLOUD_API_KEY = YOUR_CLOUD_API_KEY 4 | CLOUD_API_SECRET = YOUR_CLOUD_API_SECRET 5 | CLOUD_NAME = YOUR_CLOUD_NAME 6 | 7 | 8 | REFRESH_TOKEN_SECRET = YOUR_REFRESH_TOKEN_SECRET 9 | ACCESS_TOKEN_SECRET = YOUR_ACCESS_TOKEN_SECRET 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MERN Stack - Ecommerce 2 | > Ecommerce website built with the MERN stack with React Context API for state management, pure CSS for style 3 | 4 | ## Demo: https://devat-ecommerce.herokuapp.com/ 5 | 6 | ## Author: Dev AT 7 | ## Youtube tutorials: https://youtu.be/AjItjNc0bHA 8 | 9 | ## Install dependencies for server 10 | ### `npm install` 11 | 12 | ## Install dependencies for client 13 | ### cd client ---> `npm install` 14 | 15 | ## Connect to your mongodb and add info in .env 16 | 17 | ## Add your paypal client id in client/src/components/mainpages/cart/PaypalButton.js 18 | 19 | ## Run the client & server with concurrently 20 | ### `npm run dev` 21 | 22 | ## Run the Express server only 23 | ### `npm run server` 24 | 25 | ## Run the React client only 26 | ### `npm run client` 27 | 28 | ### Server runs on http://localhost:5000 and client on http://localhost:3000 29 | 30 | ### User interface 31 | 32 | ![alt](https://res.cloudinary.com/devatchannel/image/upload/v1599568147/test/1_pe9ism.png) 33 | 34 | ### Admin interface 35 | 36 | ![alt](https://res.cloudinary.com/devatchannel/image/upload/v1599568148/test/2_obw2r7.png) 37 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.5.0", 8 | "@testing-library/user-event": "^7.2.1", 9 | "axios": "^0.20.0", 10 | "react": "^16.13.1", 11 | "react-dom": "^16.13.1", 12 | "react-paypal-express-checkout": "^1.0.5", 13 | "react-router-dom": "^5.2.0", 14 | "react-scripts": "3.4.3" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | }, 37 | "proxy": "http://localhost:5000" 38 | } 39 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devat-youtuber/mern-ecommerce/e1760dedeb43fd9047a5d0f4334f0e75a665e0f7/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devat-youtuber/mern-ecommerce/e1760dedeb43fd9047a5d0f4334f0e75a665e0f7/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devat-youtuber/mern-ecommerce/e1760dedeb43fd9047a5d0f4334f0e75a665e0f7/client/public/logo512.png -------------------------------------------------------------------------------- /client/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 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BrowserRouter as Router} from 'react-router-dom' 3 | import {DataProvider} from './GlobalState' 4 | import Header from './components/headers/Header' 5 | import MainPages from './components/mainpages/Pages' 6 | 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /client/src/GlobalState.js: -------------------------------------------------------------------------------- 1 | import React, {createContext, useState, useEffect} from 'react' 2 | import ProductsAPI from './api/ProductsAPI' 3 | import UserAPI from './api/UserAPI' 4 | import CategoriesAPI from './api/CategoriesAPI' 5 | 6 | import axios from 'axios' 7 | 8 | export const GlobalState = createContext() 9 | 10 | 11 | export const DataProvider = ({children}) =>{ 12 | const [token, setToken] = useState(false) 13 | 14 | 15 | useEffect(() =>{ 16 | const firstLogin = localStorage.getItem('firstLogin') 17 | if(firstLogin){ 18 | const refreshToken = async () =>{ 19 | const res = await axios.get('/user/refresh_token') 20 | 21 | setToken(res.data.accesstoken) 22 | 23 | setTimeout(() => { 24 | refreshToken() 25 | }, 10 * 60 * 1000) 26 | } 27 | refreshToken() 28 | } 29 | },[]) 30 | 31 | 32 | 33 | const state = { 34 | token: [token, setToken], 35 | productsAPI: ProductsAPI(), 36 | userAPI: UserAPI(token), 37 | categoriesAPI: CategoriesAPI() 38 | } 39 | 40 | return ( 41 | 42 | {children} 43 | 44 | ) 45 | } -------------------------------------------------------------------------------- /client/src/api/CategoriesAPI.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | import axios from 'axios' 3 | 4 | function CategoriesAPI() { 5 | const [categories, setCategories] = useState([]) 6 | const [callback, setCallback] = useState(false) 7 | 8 | useEffect(() =>{ 9 | const getCategories = async () =>{ 10 | const res = await axios.get('/api/category') 11 | setCategories(res.data) 12 | } 13 | 14 | getCategories() 15 | },[callback]) 16 | return { 17 | categories: [categories, setCategories], 18 | callback: [callback, setCallback] 19 | } 20 | } 21 | 22 | export default CategoriesAPI 23 | -------------------------------------------------------------------------------- /client/src/api/ProductsAPI.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | import axios from 'axios' 3 | 4 | 5 | function ProductsAPI() { 6 | const [products, setProducts] = useState([]) 7 | const [callback, setCallback] = useState(false) 8 | const [category, setCategory] = useState('') 9 | const [sort, setSort] = useState('') 10 | const [search, setSearch] = useState('') 11 | const [page, setPage] = useState(1) 12 | const [result, setResult] = useState(0) 13 | 14 | useEffect(() =>{ 15 | const getProducts = async () => { 16 | const res = await axios.get(`/api/products?limit=${page*9}&${category}&${sort}&title[regex]=${search}`) 17 | setProducts(res.data.products) 18 | setResult(res.data.result) 19 | } 20 | getProducts() 21 | },[callback, category, sort, search, page]) 22 | 23 | return { 24 | products: [products, setProducts], 25 | callback: [callback, setCallback], 26 | category: [category, setCategory], 27 | sort: [sort, setSort], 28 | search: [search, setSearch], 29 | page: [page, setPage], 30 | result: [result, setResult] 31 | } 32 | } 33 | 34 | export default ProductsAPI 35 | -------------------------------------------------------------------------------- /client/src/api/UserAPI.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | import axios from 'axios' 3 | 4 | function UserAPI(token) { 5 | const [isLogged, setIsLogged] = useState(false) 6 | const [isAdmin, setIsAdmin] = useState(false) 7 | const [cart, setCart] = useState([]) 8 | const [history, setHistory] = useState([]) 9 | 10 | useEffect(() =>{ 11 | if(token){ 12 | const getUser = async () =>{ 13 | try { 14 | const res = await axios.get('/user/infor', { 15 | headers: {Authorization: token} 16 | }) 17 | 18 | setIsLogged(true) 19 | res.data.role === 1 ? setIsAdmin(true) : setIsAdmin(false) 20 | 21 | setCart(res.data.cart) 22 | 23 | } catch (err) { 24 | alert(err.response.data.msg) 25 | } 26 | } 27 | 28 | getUser() 29 | 30 | } 31 | },[token]) 32 | 33 | 34 | 35 | const addCart = async (product) => { 36 | if(!isLogged) return alert("Please login to continue buying") 37 | 38 | const check = cart.every(item =>{ 39 | return item._id !== product._id 40 | }) 41 | 42 | if(check){ 43 | setCart([...cart, {...product, quantity: 1}]) 44 | 45 | await axios.patch('/user/addcart', {cart: [...cart, {...product, quantity: 1}]}, { 46 | headers: {Authorization: token} 47 | }) 48 | 49 | }else{ 50 | alert("This product has been added to cart.") 51 | } 52 | } 53 | 54 | return { 55 | isLogged: [isLogged, setIsLogged], 56 | isAdmin: [isAdmin, setIsAdmin], 57 | cart: [cart, setCart], 58 | addCart: addCart, 59 | history: [history, setHistory] 60 | } 61 | } 62 | 63 | export default UserAPI 64 | -------------------------------------------------------------------------------- /client/src/components/headers/Header.js: -------------------------------------------------------------------------------- 1 | import React, {useContext, useState} from 'react' 2 | import {GlobalState} from '../../GlobalState' 3 | import Menu from './icon/menu.svg' 4 | import Close from './icon/close.svg' 5 | import Cart from './icon/cart.svg' 6 | import {Link} from 'react-router-dom' 7 | import axios from 'axios' 8 | 9 | function Header() { 10 | const state = useContext(GlobalState) 11 | const [isLogged] = state.userAPI.isLogged 12 | const [isAdmin] = state.userAPI.isAdmin 13 | const [cart] = state.userAPI.cart 14 | const [menu, setMenu] = useState(false) 15 | 16 | const logoutUser = async () =>{ 17 | await axios.get('/user/logout') 18 | 19 | localStorage.removeItem('firstLogin') 20 | 21 | window.location.href = "/"; 22 | } 23 | 24 | const adminRouter = () =>{ 25 | return( 26 | <> 27 |
  • Create Product
  • 28 |
  • Categories
  • 29 | 30 | ) 31 | } 32 | 33 | const loggedRouter = () =>{ 34 | return( 35 | <> 36 |
  • History
  • 37 |
  • Logout
  • 38 | 39 | ) 40 | } 41 | 42 | 43 | const styleMenu = { 44 | left: menu ? 0 : "-100%" 45 | } 46 | 47 | return ( 48 |
    49 |
    setMenu(!menu)}> 50 | 51 |
    52 | 53 |
    54 |

    55 | {isAdmin ? 'Admin' : 'DevAT Shop'} 56 |

    57 |
    58 | 59 | 73 | 74 | { 75 | isAdmin ? '' 76 | :
    77 | {cart.length} 78 | 79 | 80 | 81 |
    82 | } 83 | 84 |
    85 | ) 86 | } 87 | 88 | export default Header 89 | -------------------------------------------------------------------------------- /client/src/components/headers/header.css: -------------------------------------------------------------------------------- 1 | header{ 2 | min-height: 70px; 3 | width: 100%; 4 | overflow: hidden; 5 | display: flex; 6 | flex-wrap: wrap; 7 | justify-content: space-around; 8 | align-items: center; 9 | border-bottom: 1px solid #ddd; 10 | } 11 | header .logo{ 12 | flex: 1; 13 | } 14 | header a{ 15 | text-transform: uppercase; 16 | color: #555; 17 | } 18 | header ul li{ 19 | display: inline-block; 20 | opacity: 0.7; 21 | padding: 0 20px; 22 | } 23 | header ul li:hover{ 24 | opacity: 1; 25 | } 26 | .menu{ 27 | display: none; 28 | } 29 | .cart-icon{ 30 | position: relative; 31 | margin-right: 20px; 32 | } 33 | .cart-icon span{ 34 | background: crimson; 35 | border-radius: 20px; 36 | color: white; 37 | position: absolute; 38 | top:-10px; 39 | right: -10px; 40 | padding: 5px 7px; 41 | font-size: 10px; 42 | } -------------------------------------------------------------------------------- /client/src/components/headers/icon/cart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/headers/icon/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/headers/icon/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/mainpages/Pages.js: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react' 2 | import {Switch, Route} from 'react-router-dom' 3 | import Products from './products/Products' 4 | import DetailProduct from './detailProduct/DetailProduct' 5 | import Login from './auth/Login' 6 | import Register from './auth/Register' 7 | import OrderHistory from './history/OrderHistory' 8 | import OrderDetails from './history/OrderDetails' 9 | import Cart from './cart/Cart' 10 | import NotFound from './utils/not_found/NotFound' 11 | import Categories from './categories/Categories' 12 | import CreateProduct from './createProduct/CreateProduct' 13 | 14 | import {GlobalState} from '../../GlobalState' 15 | 16 | 17 | function Pages() { 18 | const state = useContext(GlobalState) 19 | const [isLogged] = state.userAPI.isLogged 20 | const [isAdmin] = state.userAPI.isAdmin 21 | 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default Pages 47 | -------------------------------------------------------------------------------- /client/src/components/mainpages/auth/Login.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {Link} from 'react-router-dom' 3 | import axios from 'axios' 4 | 5 | function Login() { 6 | const [user, setUser] = useState({ 7 | email:'', password: '' 8 | }) 9 | 10 | const onChangeInput = e =>{ 11 | const {name, value} = e.target; 12 | setUser({...user, [name]:value}) 13 | } 14 | 15 | const loginSubmit = async e =>{ 16 | e.preventDefault() 17 | try { 18 | await axios.post('/user/login', {...user}) 19 | 20 | localStorage.setItem('firstLogin', true) 21 | 22 | window.location.href = "/"; 23 | } catch (err) { 24 | alert(err.response.data.msg) 25 | } 26 | } 27 | 28 | return ( 29 |
    30 |
    31 |

    Login

    32 | 34 | 35 | 37 | 38 |
    39 | 40 | Register 41 |
    42 |
    43 |
    44 | ) 45 | } 46 | 47 | export default Login 48 | -------------------------------------------------------------------------------- /client/src/components/mainpages/auth/Register.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {Link} from 'react-router-dom' 3 | import axios from 'axios' 4 | 5 | function Register() { 6 | const [user, setUser] = useState({ 7 | name:'', email:'', password: '' 8 | }) 9 | 10 | const onChangeInput = e =>{ 11 | const {name, value} = e.target; 12 | setUser({...user, [name]:value}) 13 | } 14 | 15 | const registerSubmit = async e =>{ 16 | e.preventDefault() 17 | try { 18 | await axios.post('/user/register', {...user}) 19 | 20 | localStorage.setItem('firstLogin', true) 21 | 22 | 23 | window.location.href = "/"; 24 | } catch (err) { 25 | alert(err.response.data.msg) 26 | } 27 | } 28 | 29 | return ( 30 |
    31 |
    32 |

    Register

    33 | 35 | 36 | 38 | 39 | 41 | 42 |
    43 | 44 | Login 45 |
    46 |
    47 |
    48 | ) 49 | } 50 | 51 | export default Register -------------------------------------------------------------------------------- /client/src/components/mainpages/auth/login.css: -------------------------------------------------------------------------------- 1 | .login-page{ 2 | max-width: 500px; 3 | border: 2px solid rgb(3, 165, 206); 4 | border-radius: 5px; 5 | padding: 30px; 6 | margin: 50px auto; 7 | } 8 | .login-page h2{ 9 | text-transform: uppercase; 10 | letter-spacing: 2px; 11 | color: #555; 12 | } 13 | .login-page form input, 14 | .login-page form button{ 15 | width: 100%; 16 | height: 40px; 17 | margin: 10px 0; 18 | padding: 0 5px; 19 | outline: rgb(3, 165, 206); 20 | border: 1px solid rgb(3, 165, 206); 21 | } 22 | .login-page form .row{ 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | } 27 | .login-page form button{ 28 | width: 150px; 29 | background: rgb(3, 165, 206); 30 | color: white; 31 | text-transform: uppercase; 32 | letter-spacing: 2px; 33 | } 34 | .login-page form a{ 35 | color: orange; 36 | letter-spacing: 1.3px; 37 | text-transform: uppercase; 38 | } -------------------------------------------------------------------------------- /client/src/components/mainpages/cart/Cart.js: -------------------------------------------------------------------------------- 1 | import React, {useContext, useState, useEffect} from 'react' 2 | import {GlobalState} from '../../../GlobalState' 3 | import axios from 'axios' 4 | import PaypalButton from './PaypalButton' 5 | 6 | function Cart() { 7 | const state = useContext(GlobalState) 8 | const [cart, setCart] = state.userAPI.cart 9 | const [token] = state.token 10 | const [total, setTotal] = useState(0) 11 | 12 | useEffect(() =>{ 13 | const getTotal = () =>{ 14 | const total = cart.reduce((prev, item) => { 15 | return prev + (item.price * item.quantity) 16 | },0) 17 | 18 | setTotal(total) 19 | } 20 | 21 | getTotal() 22 | 23 | },[cart]) 24 | 25 | const addToCart = async (cart) =>{ 26 | await axios.patch('/user/addcart', {cart}, { 27 | headers: {Authorization: token} 28 | }) 29 | } 30 | 31 | 32 | const increment = (id) =>{ 33 | cart.forEach(item => { 34 | if(item._id === id){ 35 | item.quantity += 1 36 | } 37 | }) 38 | 39 | setCart([...cart]) 40 | addToCart(cart) 41 | } 42 | 43 | const decrement = (id) =>{ 44 | cart.forEach(item => { 45 | if(item._id === id){ 46 | item.quantity === 1 ? item.quantity = 1 : item.quantity -= 1 47 | } 48 | }) 49 | 50 | setCart([...cart]) 51 | addToCart(cart) 52 | } 53 | 54 | const removeProduct = id =>{ 55 | if(window.confirm("Do you want to delete this product?")){ 56 | cart.forEach((item, index) => { 57 | if(item._id === id){ 58 | cart.splice(index, 1) 59 | } 60 | }) 61 | 62 | setCart([...cart]) 63 | addToCart(cart) 64 | } 65 | } 66 | 67 | const tranSuccess = async(payment) => { 68 | const {paymentID, address} = payment; 69 | 70 | await axios.post('/api/payment', {cart, paymentID, address}, { 71 | headers: {Authorization: token} 72 | }) 73 | 74 | setCart([]) 75 | addToCart([]) 76 | alert("You have successfully placed an order.") 77 | } 78 | 79 | 80 | if(cart.length === 0) 81 | return

    Cart Empty

    82 | 83 | return ( 84 |
    85 | { 86 | cart.map(product => ( 87 |
    88 | 89 | 90 |
    91 |

    {product.title}

    92 | 93 |

    $ {product.price * product.quantity}

    94 |

    {product.description}

    95 |

    {product.content}

    96 | 97 |
    98 | 99 | {product.quantity} 100 | 101 |
    102 | 103 |
    removeProduct(product._id)}> 105 | X 106 |
    107 |
    108 |
    109 | )) 110 | } 111 | 112 |
    113 |

    Total: $ {total}

    114 | 117 |
    118 |
    119 | ) 120 | } 121 | 122 | export default Cart 123 | -------------------------------------------------------------------------------- /client/src/components/mainpages/cart/PaypalButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PaypalExpressBtn from 'react-paypal-express-checkout'; 3 | 4 | export default class PaypalButton extends React.Component { 5 | render() { 6 | const onSuccess = (payment) => { 7 | // Congratulation, it came here means everything's fine! 8 | console.log("The payment was succeeded!", payment); 9 | // You can bind the "payment" object's value to your state or props or whatever here, please see below for sample returned data 10 | this.props.tranSuccess(payment) 11 | } 12 | 13 | const onCancel = (data) => { 14 | // User pressed "cancel" or close Paypal's popup! 15 | console.log('The payment was cancelled!', data); 16 | // You can bind the "data" object's value to your state or props or whatever here, please see below for sample returned data 17 | } 18 | 19 | const onError = (err) => { 20 | // The main Paypal's script cannot be loaded or somethings block the loading of that script! 21 | console.log("Error!", err); 22 | // Because the Paypal's main script is loaded asynchronously from "https://www.paypalobjects.com/api/checkout.js" 23 | // => sometimes it may take about 0.5 second for everything to get set, or for the button to appear 24 | } 25 | 26 | let env = 'sandbox'; // you can set here to 'production' for production 27 | let currency = 'USD'; // or you can set this value from your props or state 28 | let total = this.props.total; // same as above, this is the total amount (based on currency) to be paid by using Paypal express checkout 29 | // Document on Paypal's currency code: https://developer.paypal.com/docs/classic/api/currency_codes/ 30 | 31 | const client = { 32 | sandbox: 'YOUR-sandbox-APP-ID', 33 | production: 'YOUR-PRODUCTION-APP-ID', 34 | } 35 | // In order to get production's app-ID, you will have to send your app to Paypal for approval first 36 | // For sandbox app-ID (after logging into your developer account, please locate the "REST API apps" section, click "Create App"): 37 | // => https://developer.paypal.com/docs/classic/lifecycle/sb_credentials/ 38 | // For production app-ID: 39 | // => https://developer.paypal.com/docs/classic/lifecycle/goingLive/ 40 | 41 | // NB. You can also have many Paypal express checkout buttons on page, just pass in the correct amount and they will work! 42 | let style = { 43 | size: 'small', 44 | color: 'blue', 45 | shape: 'rect', 46 | label: 'checkout', 47 | tagline: false 48 | } 49 | 50 | return ( 51 | 57 | ); 58 | } 59 | } -------------------------------------------------------------------------------- /client/src/components/mainpages/cart/cart.css: -------------------------------------------------------------------------------- 1 | .cart{ 2 | position: relative; 3 | border: 1px solid #ccc; 4 | transform: scaleY(0.98); 5 | } 6 | .amount span{ 7 | color: crimson; 8 | padding: 0 20px; 9 | } 10 | .amount button{ 11 | width: 40px; 12 | height: 40px; 13 | border: 1px solid #777; 14 | } 15 | .delete{ 16 | position: absolute; 17 | top:0; 18 | right: 5px; 19 | color: crimson; 20 | font-weight: 900; 21 | cursor: pointer; 22 | } 23 | .total{ 24 | width: 100%; 25 | height: 50px; 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-between; 29 | } 30 | .total h3{ 31 | color: crimson; 32 | } -------------------------------------------------------------------------------- /client/src/components/mainpages/categories/Categories.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useContext} from 'react' 2 | import {GlobalState} from '../../../GlobalState' 3 | import axios from 'axios' 4 | 5 | function Categories() { 6 | const state = useContext(GlobalState) 7 | const [categories] = state.categoriesAPI.categories 8 | const [category, setCategory] = useState('') 9 | const [token] = state.token 10 | const [callback, setCallback] = state.categoriesAPI.callback 11 | const [onEdit, setOnEdit] = useState(false) 12 | const [id, setID] = useState('') 13 | 14 | const createCategory = async e =>{ 15 | e.preventDefault() 16 | try { 17 | if(onEdit){ 18 | const res = await axios.put(`/api/category/${id}`, {name: category}, { 19 | headers: {Authorization: token} 20 | }) 21 | alert(res.data.msg) 22 | }else{ 23 | const res = await axios.post('/api/category', {name: category}, { 24 | headers: {Authorization: token} 25 | }) 26 | alert(res.data.msg) 27 | } 28 | setOnEdit(false) 29 | setCategory('') 30 | setCallback(!callback) 31 | 32 | } catch (err) { 33 | alert(err.response.data.msg) 34 | } 35 | } 36 | 37 | const editCategory = async (id, name) =>{ 38 | setID(id) 39 | setCategory(name) 40 | setOnEdit(true) 41 | } 42 | 43 | const deleteCategory = async id =>{ 44 | try { 45 | const res = await axios.delete(`/api/category/${id}`, { 46 | headers: {Authorization: token} 47 | }) 48 | alert(res.data.msg) 49 | setCallback(!callback) 50 | } catch (err) { 51 | alert(err.response.data.msg) 52 | } 53 | } 54 | 55 | return ( 56 |
    57 |
    58 | 59 | setCategory(e.target.value)} /> 61 | 62 | 63 |
    64 | 65 |
    66 | { 67 | categories.map(category => ( 68 |
    69 |

    {category.name}

    70 |
    71 | 72 | 73 |
    74 |
    75 | )) 76 | } 77 |
    78 |
    79 | ) 80 | } 81 | 82 | export default Categories 83 | -------------------------------------------------------------------------------- /client/src/components/mainpages/categories/categories.css: -------------------------------------------------------------------------------- 1 | .categories{ 2 | max-width: 700px; 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: space-around; 6 | margin: 30px auto; 7 | } 8 | .categories form{ 9 | width: 290px; 10 | margin-bottom: 20px; 11 | } 12 | .categories label{ 13 | display: block; 14 | font-weight: 700; 15 | letter-spacing: 2px; 16 | text-transform: uppercase; 17 | margin-bottom: 10px; 18 | } 19 | .categories input, button{ 20 | height: 35px; 21 | border: none; 22 | outline: none; 23 | border-bottom: 1px solid #555; 24 | } 25 | .categories input{ 26 | width: 210px; 27 | } 28 | .categories button{ 29 | width: 70px; 30 | background: #555; 31 | color: white; 32 | margin-left: 10px; 33 | } 34 | .categories .row{ 35 | min-width: 290px; 36 | display: flex; 37 | justify-content: space-between; 38 | align-items: center; 39 | padding: 10px; 40 | margin-bottom: 10px; 41 | border: 1px solid #ccc; 42 | } -------------------------------------------------------------------------------- /client/src/components/mainpages/createProduct/CreateProduct.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useContext, useEffect} from 'react' 2 | import axios from 'axios' 3 | import {GlobalState} from '../../../GlobalState' 4 | import Loading from '../utils/loading/Loading' 5 | import {useHistory, useParams} from 'react-router-dom' 6 | 7 | const initialState = { 8 | product_id: '', 9 | title: '', 10 | price: 0, 11 | description: 'How to and tutorial videos of cool CSS effect, Web Design ideas,JavaScript libraries, Node.', 12 | content: 'Welcome to our channel Dev AT. Here you can learn web designing, UI/UX designing, html css tutorials, css animations and css effects, javascript and jquery tutorials and related so on.', 13 | category: '', 14 | _id: '' 15 | } 16 | 17 | function CreateProduct() { 18 | const state = useContext(GlobalState) 19 | const [product, setProduct] = useState(initialState) 20 | const [categories] = state.categoriesAPI.categories 21 | const [images, setImages] = useState(false) 22 | const [loading, setLoading] = useState(false) 23 | 24 | 25 | const [isAdmin] = state.userAPI.isAdmin 26 | const [token] = state.token 27 | 28 | const history = useHistory() 29 | const param = useParams() 30 | 31 | const [products] = state.productsAPI.products 32 | const [onEdit, setOnEdit] = useState(false) 33 | const [callback, setCallback] = state.productsAPI.callback 34 | 35 | useEffect(() => { 36 | if(param.id){ 37 | setOnEdit(true) 38 | products.forEach(product => { 39 | if(product._id === param.id) { 40 | setProduct(product) 41 | setImages(product.images) 42 | } 43 | }) 44 | }else{ 45 | setOnEdit(false) 46 | setProduct(initialState) 47 | setImages(false) 48 | } 49 | }, [param.id, products]) 50 | 51 | const handleUpload = async e =>{ 52 | e.preventDefault() 53 | try { 54 | if(!isAdmin) return alert("You're not an admin") 55 | const file = e.target.files[0] 56 | 57 | if(!file) return alert("File not exist.") 58 | 59 | if(file.size > 1024 * 1024) // 1mb 60 | return alert("Size too large!") 61 | 62 | if(file.type !== 'image/jpeg' && file.type !== 'image/png') // 1mb 63 | return alert("File format is incorrect.") 64 | 65 | let formData = new FormData() 66 | formData.append('file', file) 67 | 68 | setLoading(true) 69 | const res = await axios.post('/api/upload', formData, { 70 | headers: {'content-type': 'multipart/form-data', Authorization: token} 71 | }) 72 | setLoading(false) 73 | setImages(res.data) 74 | 75 | } catch (err) { 76 | alert(err.response.data.msg) 77 | } 78 | } 79 | 80 | const handleDestroy = async () => { 81 | try { 82 | if(!isAdmin) return alert("You're not an admin") 83 | setLoading(true) 84 | await axios.post('/api/destroy', {public_id: images.public_id}, { 85 | headers: {Authorization: token} 86 | }) 87 | setLoading(false) 88 | setImages(false) 89 | } catch (err) { 90 | alert(err.response.data.msg) 91 | } 92 | } 93 | 94 | const handleChangeInput = e =>{ 95 | const {name, value} = e.target 96 | setProduct({...product, [name]:value}) 97 | } 98 | 99 | const handleSubmit = async e =>{ 100 | e.preventDefault() 101 | try { 102 | if(!isAdmin) return alert("You're not an admin") 103 | if(!images) return alert("No Image Upload") 104 | 105 | if(onEdit){ 106 | await axios.put(`/api/products/${product._id}`, {...product, images}, { 107 | headers: {Authorization: token} 108 | }) 109 | }else{ 110 | await axios.post('/api/products', {...product, images}, { 111 | headers: {Authorization: token} 112 | }) 113 | } 114 | setCallback(!callback) 115 | history.push("/") 116 | } catch (err) { 117 | alert(err.response.data.msg) 118 | } 119 | } 120 | 121 | const styleUpload = { 122 | display: images ? "block" : "none" 123 | } 124 | return ( 125 |
    126 |
    127 | 128 | { 129 | loading ?
    130 | 131 | :
    132 | 133 | X 134 |
    135 | } 136 | 137 |
    138 | 139 |
    140 |
    141 | 142 | 144 |
    145 | 146 |
    147 | 148 | 150 |
    151 | 152 |
    153 | 154 | 156 |
    157 | 158 |
    159 | 160 |