├── shopitel-client-react-ui ├── src │ ├── index.css │ ├── images │ │ └── Shopitel.PNG │ ├── components │ │ ├── Announcement.jsx │ │ ├── Categories.jsx │ │ ├── CategoryItem.jsx │ │ ├── Newsletter.jsx │ │ ├── Product.jsx │ │ ├── Products.jsx │ │ ├── Slider.jsx │ │ ├── Navbar.jsx │ │ └── Footer.jsx │ ├── index.js │ ├── requestMethods.js │ ├── redux │ │ ├── cartRedux.js │ │ ├── apiCalls.js │ │ ├── store.js │ │ └── userRedux.js │ ├── pages │ │ ├── Home.jsx │ │ ├── Success.jsx │ │ ├── Login.jsx │ │ ├── ProductList.jsx │ │ ├── Register.jsx │ │ ├── Product.jsx │ │ └── Cart.jsx │ ├── App.jsx │ └── data.js ├── public │ ├── robots.txt │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── shopitel-api ├── .gitignore ├── models │ ├── User.js │ ├── Cart.js │ ├── Product.js │ └── Order.js ├── routes │ ├── stripe.js │ ├── verifyToken.js │ ├── cart.js │ ├── product.js │ ├── auth.js │ ├── order.js │ └── user.js ├── package.json ├── index.js └── yarn.lock ├── LICENSE ├── README.md └── .github └── workflows └── codeql-analysis.yml /shopitel-client-react-ui/src/index.css: -------------------------------------------------------------------------------- 1 | #inputID::placeholder { 2 | color: white; 3 | font-size: 16px; 4 | } 5 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /shopitel-api/.gitignore: -------------------------------------------------------------------------------- 1 | .env.local 2 | .env.development.local 3 | .env.test.local 4 | .env.production.local 5 | .env 6 | 7 | # dependencies 8 | /node_modules -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/images/Shopitel.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbihaFatima/shopitel-ecommerce-app/HEAD/shopitel-client-react-ui/src/images/Shopitel.PNG -------------------------------------------------------------------------------- /shopitel-client-react-ui/.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 | .env 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 | -------------------------------------------------------------------------------- /shopitel-api/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const UserSchema = new mongoose.Schema( 4 | { 5 | username: { type: String, required: true, unique: true }, 6 | email: { type: String, required: true, unique: true }, 7 | password: { type: String, required: true }, 8 | isAdmin: { 9 | type: Boolean, 10 | default: false, 11 | }, 12 | }, 13 | { timestamps: true } 14 | ); 15 | 16 | module.exports = mongoose.model("User", UserSchema); -------------------------------------------------------------------------------- /shopitel-api/models/Cart.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const CartSchema = new mongoose.Schema( 4 | { 5 | userId: { type: String, required: true }, 6 | products: [ 7 | { 8 | productId: { 9 | type: String, 10 | }, 11 | quantity: { 12 | type: Number, 13 | default: 1, 14 | }, 15 | }, 16 | ], 17 | }, 18 | { timestamps: true } 19 | ); 20 | 21 | module.exports = mongoose.model("Cart", CartSchema); -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/Announcement.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Container = styled.div` 4 | height: 40px; 5 | background-color: #d5cdd5; 6 | color: black; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | font-size: 16px; 11 | font-weight: 500; 12 | `; 13 | 14 | const Announcement = () => { 15 | return Super Deal! Free Shipping on Orders Over $ 50; 16 | }; 17 | 18 | export default Announcement; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import {Provider} from "react-redux" 5 | import { store, persistor } from "./redux/store"; 6 | import { PersistGate } from 'redux-persist/integration/react' 7 | 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/requestMethods.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from "axios"; 3 | 4 | const BASE_URL = "http://localhost:3000/api/"; 5 | 6 | const user = JSON.parse(localStorage.getItem("persist:root"))?.user; 7 | const currentUser = user && JSON.parse(user).currentUser; 8 | const TOKEN = currentUser?.accessToken; 9 | 10 | export const publicRequest = axios.create({ 11 | baseURL: BASE_URL, 12 | }); 13 | 14 | export const userRequest = axios.create({ 15 | baseURL: BASE_URL, 16 | header: { token: `Bearer ${TOKEN}` }, 17 | }); -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/Categories.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { categories } from "../data"; 3 | import CategoryItem from "./CategoryItem"; 4 | 5 | const Container = styled.div` 6 | display: flex; 7 | padding: 20px; 8 | justify-content: space-between; 9 | `; 10 | 11 | const Categories = () => { 12 | return ( 13 | 14 | {categories.map((item) => ( 15 | 16 | ))} 17 | 18 | ); 19 | }; 20 | 21 | export default Categories; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/redux/cartRedux.js: -------------------------------------------------------------------------------- 1 | 2 | import { createSlice } from "@reduxjs/toolkit"; 3 | 4 | const cartSlice = createSlice({ 5 | name: "cart", 6 | initialState: { 7 | products: [], 8 | quantity: 0, 9 | total: 0, 10 | }, 11 | reducers: { 12 | addProduct: (state, action) => { 13 | state.quantity += 1; 14 | state.products.push(action.payload); 15 | state.total += action.payload.price * action.payload.quantity; 16 | }, 17 | }, 18 | }); 19 | 20 | export const { addProduct } = cartSlice.actions; 21 | export default cartSlice.reducer; -------------------------------------------------------------------------------- /shopitel-api/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const ProductSchema = new mongoose.Schema( 4 | { 5 | title: { type: String, required: true, unique: true }, 6 | desc: { type: String, required: true, }, 7 | img: { type: String, required: true }, 8 | categories: { type: Array }, 9 | size: { type: Array }, 10 | color: { type: Array }, 11 | price: { type: Number, required: true }, 12 | inStock: { type: Boolean, default: true }, 13 | }, 14 | { timestamps: true } 15 | ); 16 | 17 | module.exports = mongoose.model("Product", ProductSchema); -------------------------------------------------------------------------------- /shopitel-api/routes/stripe.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const KEY = process.env.STRIPE_KEY 3 | const stripe = require("stripe")(KEY); 4 | 5 | 6 | router.post("/payment", (req, res) => { 7 | stripe.charges.create( 8 | { 9 | source: req.body.tokenId, 10 | amount: req.body.amount, 11 | currency: "AED", 12 | }, 13 | (stripeErr, stripeRes) => { 14 | if (stripeErr) { 15 | res.status(500).json(stripeErr); 16 | } else { 17 | res.status(200).json(stripeRes); 18 | } 19 | } 20 | ); 21 | }); 22 | 23 | module.exports = router; -------------------------------------------------------------------------------- /shopitel-client-react-ui/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 | -------------------------------------------------------------------------------- /shopitel-api/models/Order.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const OrderSchema = new mongoose.Schema( 4 | { 5 | userId: { type: String, required: true }, 6 | products: [ 7 | { 8 | productId: { 9 | type: String, 10 | }, 11 | quantity: { 12 | type: Number, 13 | default: 1, 14 | }, 15 | }, 16 | ], 17 | amount: { type: Number, required: true }, 18 | address: { type: Object, required: true }, 19 | status: { type: String, default: "pending" }, 20 | }, 21 | { timestamps: true } 22 | ); 23 | 24 | module.exports = mongoose.model("Order", OrderSchema); -------------------------------------------------------------------------------- /shopitel-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shopitel-API", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cookie-parser": "^1.4.6", 14 | "cors": "^2.8.5", 15 | "crypto-js": "^4.1.1", 16 | "dotenv": "^10.0.0", 17 | "express": "^4.17.1", 18 | "express-session": "^1.17.2", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^6.0.13", 21 | "nodemon": "^2.0.15", 22 | "passport": "^0.5.0", 23 | "stripe": "^8.191.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import Announcement from "../components/Announcement"; 2 | import React from 'react' 3 | import Navbar from '../components/Navbar' 4 | import Slider from "../components/Slider" 5 | import Categories from "../components/Categories"; 6 | import Products from "../components/Products"; 7 | import Newsletter from "../components/Newsletter"; 8 | import Footer from "../components/Footer"; 9 | 10 | const Home = () => { 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
21 | ) 22 | } 23 | 24 | export default Home 25 | 26 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | React App 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/redux/apiCalls.js: -------------------------------------------------------------------------------- 1 | import { loginFailure, loginStart, loginSuccess, registerFailure,registerStart, registerSuccess,logout } from "./userRedux"; 2 | import { publicRequest } from "../requestMethods"; 3 | 4 | export const login = async (dispatch, user) => { 5 | dispatch(loginStart()); 6 | try { 7 | const res = await publicRequest.post("/auth/login", user); 8 | dispatch(loginSuccess(res.data)); 9 | } catch (err) { 10 | dispatch(loginFailure()); 11 | } 12 | }; 13 | export const register = async (dispatch, user) => { 14 | dispatch(registerStart()); 15 | try { 16 | const res = await publicRequest.post("/auth/register", user); 17 | dispatch(registerSuccess(res.data)); 18 | } catch (err) { 19 | dispatch(registerFailure()); 20 | } 21 | }; 22 | 23 | export const logOut = async (dispatch, user) => { 24 | try { 25 | const res = await publicRequest.delete("/logout", user); 26 | dispatch(logout(res.data)); 27 | } catch (err) { 28 | 29 | } 30 | }; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore, combineReducers } from "@reduxjs/toolkit"; 2 | import cartReducer from "./cartRedux"; 3 | import userReducer from "./userRedux"; 4 | import { 5 | persistStore, 6 | persistReducer, 7 | FLUSH, 8 | REHYDRATE, 9 | PAUSE, 10 | PERSIST, 11 | PURGE, 12 | REGISTER, 13 | } from "redux-persist"; 14 | import storage from "redux-persist/lib/storage"; 15 | 16 | const persistConfig = { 17 | key: "root", 18 | version: 1, 19 | storage, 20 | }; 21 | 22 | const rootReducer = combineReducers({ user: userReducer, cart: cartReducer }); 23 | 24 | const persistedReducer = persistReducer(persistConfig, rootReducer); 25 | 26 | export const store = configureStore({ 27 | reducer: persistedReducer, 28 | middleware: (getDefaultMiddleware) => 29 | getDefaultMiddleware({ 30 | serializableCheck: { 31 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 32 | }, 33 | }), 34 | }); 35 | 36 | export let persistor = persistStore(store); -------------------------------------------------------------------------------- /shopitel-api/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const mongoose = require("mongoose"); 4 | const dotenv = require("dotenv"); 5 | const userRoute = require("./routes/user") 6 | const authRoute = require("./routes/auth") 7 | const productRoute = require("./routes/product"); 8 | const cartRoute = require("./routes/cart"); 9 | const orderRoute = require("./routes/order"); 10 | const stripeRoute = require("./routes/stripe"); 11 | const cors = require("cors"); 12 | 13 | dotenv.config(); 14 | 15 | mongoose.connect(process.env.MONGO_URL) 16 | .then(()=>console.log("Database connection successful")) 17 | .catch((err)=>{ 18 | console.log(err); 19 | }); 20 | 21 | app.use(cors()); 22 | app.use(express.json()); 23 | app.use("/api/auth", authRoute); 24 | app.use("/api/user", userRoute); 25 | app.use("/api/products", productRoute); 26 | app.use("/api/carts", cartRoute); 27 | app.use("/api/orders", orderRoute); 28 | app.use("/api/checkout", stripeRoute); 29 | 30 | app.listen(process.env.PORT || 3000, ()=>{ 31 | console.log("Backend server is runnning!"); 32 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Abiha Fatima 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /shopitel-api/routes/verifyToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const verifyToken = (req, res, next) => { 4 | const authHeader = req.headers.token; 5 | if (authHeader) { 6 | const token = authHeader.split(" ")[1]; 7 | jwt.verify(token, process.env.JWT_SEC, (err, user) => { 8 | if (err) res.status(403).json("Token is not valid!"); 9 | req.user = user; 10 | next(); 11 | }); 12 | } else { 13 | return res.status(401).json("You are not authenticated!"); 14 | } 15 | }; 16 | 17 | const verifyTokenAndAuthorization = (req, res, next) => { 18 | verifyToken(req, res, () => { 19 | if (req.user.id === req.params.id || req.user.isAdmin) { 20 | next(); 21 | } else { 22 | res.status(403).json("You are not alowed to do that!"); 23 | } 24 | }); 25 | }; 26 | 27 | const verifyTokenAndAdmin = (req, res, next) => { 28 | verifyToken(req, res, () => { 29 | if (req.user.isAdmin) { 30 | next(); 31 | } else { 32 | res.status(403).json("You are not alowed to do that!"); 33 | } 34 | }); 35 | }; 36 | 37 | module.exports = { 38 | verifyToken, 39 | verifyTokenAndAuthorization, 40 | verifyTokenAndAdmin, 41 | }; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/CategoryItem.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import styled from "styled-components"; 3 | 4 | const Container = styled.div` 5 | flex: 1; 6 | margin: 5px; 7 | height: 70vh; 8 | position: relative; 9 | `; 10 | 11 | const Image = styled.img` 12 | width: 100%; 13 | height: 100%; 14 | object-fit: cover; 15 | `; 16 | 17 | const Info = styled.div` 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 100%; 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | `; 28 | 29 | const Title = styled.h1` 30 | color:white; 31 | margin-bottom: 20px; 32 | `; 33 | 34 | const Button = styled.button` 35 | border:none; 36 | padding: 10px; 37 | background-color: white; 38 | color:gray; 39 | cursor: pointer; 40 | font-weight: 600; 41 | `; 42 | 43 | const CategoryItem = ({ item }) => { 44 | return ( 45 | 46 | 47 | 48 | 49 | {item.title} 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | export default CategoryItem; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/redux/userRedux.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const userSlice = createSlice({ 4 | name: "user", 5 | initialState: { 6 | currentUser : null, 7 | isFetching : false, 8 | error : false 9 | }, 10 | reducers: { 11 | loginStart: (state) => { 12 | state.isFetching = true; 13 | }, 14 | loginSuccess: (state, action) => { 15 | state.isFetching = false; 16 | state.currentUser = action.payload; 17 | }, 18 | loginFailure: (state) => { 19 | state.isFetching = false; 20 | state.error = true; 21 | }, 22 | registerStart: (state) => { 23 | state.isFetching = true; 24 | }, 25 | registerSuccess: (state, action) => { 26 | state.isFetching = false; 27 | state.currentUser = action.payload; 28 | }, 29 | registerFailure: (state) => { 30 | state.isFetching = false; 31 | state.error = true; 32 | }, 33 | 34 | }, 35 | logout: (state) => { 36 | state.currentUser = null; 37 | }, 38 | }); 39 | 40 | export const { loginStart, loginSuccess, loginFailure, registerStart, registerSuccess, registerFailure,logout } = userSlice.actions; 41 | export default userSlice.reducer; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/App.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Product from "./pages/Product"; 3 | import Home from "./pages/Home"; 4 | import ProductList from "./pages/ProductList"; 5 | import Register from "./pages/Register"; 6 | import Login from "./pages/Login"; 7 | import Cart from "./pages/Cart"; 8 | import { 9 | BrowserRouter as Router, 10 | Switch, 11 | Route, 12 | Redirect, 13 | } from "react-router-dom"; 14 | import Success from "./pages/Success"; 15 | import { useSelector } from "react-redux"; 16 | 17 | const App = () => { 18 | const user = useSelector((state) => state.user.currentUser); 19 | // const user = false; 20 | //localStorage.clear(); 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {user ? : } 40 | 41 | {user ? : } 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default App; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/Newsletter.jsx: -------------------------------------------------------------------------------- 1 | import { Send } from "@material-ui/icons"; 2 | import styled from "styled-components"; 3 | 4 | const Container = styled.div` 5 | height: 60vh; 6 | background-color: #fcf5f5; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | flex-direction: column; 11 | `; 12 | const Title = styled.h1` 13 | font-size: 70px; 14 | margin-bottom: 20px; 15 | `; 16 | 17 | const Desc = styled.div` 18 | font-size: 24px; 19 | font-weight: 300; 20 | margin-bottom: 20px; 21 | `; 22 | 23 | const InputContainer = styled.div` 24 | width: 50%; 25 | height: 40px; 26 | background-color: white; 27 | display: flex; 28 | justify-content: space-between; 29 | border: 1px solid lightgray; 30 | `; 31 | 32 | const Input = styled.input` 33 | border: none; 34 | flex: 8; 35 | padding-left: 20px; 36 | `; 37 | 38 | const Button = styled.button` 39 | flex: 1; 40 | border: none; 41 | background-color: teal; 42 | color: white; 43 | `; 44 | 45 | const Newsletter = () => { 46 | return ( 47 | 48 | Newsletter 49 | Get timely updates from your favorite products. 50 | 51 | 52 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | export default Newsletter; -------------------------------------------------------------------------------- /shopitel-client-react-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopitel-client-react-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.12.3", 7 | "@material-ui/icons": "^4.11.2", 8 | "@reduxjs/toolkit": "^1.6.2", 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.24.0", 13 | "dotenv": "^10.0.0", 14 | "react": "^17.0.2", 15 | "react-bootstrap": "^2.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-redux": "^7.2.6", 18 | "react-router-dom": "5.3.0", 19 | "react-scripts": "4.0.3", 20 | "react-stripe-checkout": "^2.6.3", 21 | "redux-persist": "^6.0.0", 22 | "semantic-ui-react": "^2.0.4", 23 | "styled-components": "^5.3.3", 24 | "web-vitals": "^1.0.1" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/pages/Success.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { useEffect, useState } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { useLocation } from "react-router"; 5 | import { userRequest } from "../requestMethods"; 6 | 7 | const Success = () => { 8 | const location = useLocation(); 9 | const data = location.state.stripeData; 10 | const cart = location.state.cart; 11 | const currentUser = useSelector((state) => state.user.currentUser); 12 | const [orderId, setOrderId] = useState(null); 13 | 14 | useEffect(() => { 15 | const createOrder = async () => { 16 | try { 17 | const res = await userRequest.post("/orders", { 18 | userId: currentUser._id, 19 | products: cart.products.map((item) => ({ 20 | productId: item._id, 21 | quantity: item._quantity, 22 | })), 23 | amount: cart.total, 24 | address: data.billing_details.address, 25 | }); 26 | setOrderId(res.data._id); 27 | } catch {} 28 | }; 29 | data && createOrder(); 30 | }, [cart, data, currentUser]); 31 | 32 | return ( 33 |
42 | {orderId 43 | ? `Order has been created successfully. Your order number is ${orderId}` 44 | : `Successfull. Your order is being prepared...`} 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default Success; -------------------------------------------------------------------------------- /shopitel-api/routes/cart.js: -------------------------------------------------------------------------------- 1 | const Cart = require("../models/Cart"); 2 | const { 3 | verifyToken, 4 | verifyTokenAndAuthorization, 5 | verifyTokenAndAdmin, 6 | } = require("./verifyToken"); 7 | 8 | const router = require("express").Router(); 9 | 10 | //CREATE 11 | 12 | router.post("/", verifyToken, async (req, res) => { 13 | const newCart = new Cart(req.body); 14 | 15 | try { 16 | const savedCart = await newCart.save(); 17 | res.status(200).json(savedCart); 18 | } catch (err) { 19 | res.status(500).json(err); 20 | } 21 | }); 22 | 23 | //UPDATE 24 | router.put("/:id", verifyTokenAndAuthorization, async (req, res) => { 25 | try { 26 | const updatedCart = await Cart.findByIdAndUpdate( 27 | req.params.id, 28 | { 29 | $set: req.body, 30 | }, 31 | { new: true } 32 | ); 33 | res.status(200).json(updatedCart); 34 | } catch (err) { 35 | res.status(500).json(err); 36 | } 37 | }); 38 | 39 | //DELETE 40 | router.delete("/:id", verifyTokenAndAuthorization, async (req, res) => { 41 | try { 42 | await Cart.findByIdAndDelete(req.params.id); 43 | res.status(200).json("Cart has been deleted..."); 44 | } catch (err) { 45 | res.status(500).json(err); 46 | } 47 | }); 48 | 49 | //GET USER CART 50 | router.get("/find/:userId", verifyTokenAndAuthorization, async (req, res) => { 51 | try { 52 | const cart = await Cart.findOne({ userId: req.params.userId }); 53 | res.status(200).json(cart); 54 | } catch (err) { 55 | res.status(500).json(err); 56 | } 57 | }); 58 | 59 | // //GET ALL 60 | 61 | router.get("/", verifyTokenAndAdmin, async (req, res) => { 62 | try { 63 | const carts = await Cart.find(); 64 | res.status(200).json(carts); 65 | } catch (err) { 66 | res.status(500).json(err); 67 | } 68 | }); 69 | 70 | module.exports = router; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/Product.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | FavoriteBorderOutlined, 3 | SearchOutlined, 4 | ShoppingCartOutlined, 5 | } from "@material-ui/icons"; 6 | import { Link } from "react-router-dom"; 7 | import styled from "styled-components"; 8 | 9 | const Info = styled.div` 10 | opacity: 0; 11 | width: 100%; 12 | height: 100%; 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | background-color: rgba(0, 0, 0, 0.2); 17 | z-index: 3; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | transition: all 0.5s ease; 22 | cursor: pointer; 23 | `; 24 | 25 | const Container = styled.div` 26 | flex: 1; 27 | margin: 5px; 28 | min-width: 280px; 29 | height: 350px; 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | background-color: #f5fbfd; 34 | position: relative; 35 | &:hover ${Info}{ 36 | opacity: 1; 37 | } 38 | `; 39 | 40 | const Circle = styled.div` 41 | width: 200px; 42 | height: 200px; 43 | border-radius: 50%; 44 | background-color: white; 45 | position: absolute; 46 | `; 47 | 48 | const Image = styled.img` 49 | height: 75%; 50 | z-index: 2; 51 | `; 52 | 53 | const Icon = styled.div` 54 | width: 40px; 55 | height: 40px; 56 | border-radius: 50%; 57 | background-color: white; 58 | display: flex; 59 | align-items: center; 60 | justify-content: center; 61 | margin: 10px; 62 | transition: all 0.5s ease; 63 | &:hover { 64 | background-color: #e9f5f5; 65 | transform: scale(1.1); 66 | } 67 | `; 68 | 69 | const Product = ({ item }) => { 70 | return ( 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ); 89 | }; 90 | 91 | export default Product; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/components/Products.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import styled from "styled-components"; 3 | import { popularProducts } from "../data"; 4 | import Product from "./Product"; 5 | import axios from "axios"; 6 | 7 | const Container = styled.div` 8 | padding: 20px; 9 | display: flex; 10 | flex-wrap: wrap; 11 | justify-content: space-between; 12 | `; 13 | 14 | const Products = ({ cat, filters, sort }) => { 15 | const [products, setProducts] = useState([]); 16 | const [filteredProducts, setFilteredProducts] = useState([]); 17 | 18 | useEffect(() => { 19 | const getProducts = async () => { 20 | try { 21 | const res = await axios.get( 22 | cat 23 | ? `http://localhost:3000/api/products?category=${cat}` 24 | : "http://localhost:3000/api/products" 25 | ); 26 | setProducts(res.data); 27 | } catch (err) {} 28 | }; 29 | getProducts(); 30 | }, [cat]); 31 | 32 | useEffect(() => { 33 | cat && 34 | setFilteredProducts( 35 | products.filter((item) => 36 | Object.entries(filters).every(([key, value]) => 37 | item[key].includes(value) 38 | ) 39 | ) 40 | ); 41 | }, [products, cat, filters]); 42 | 43 | useEffect(() => { 44 | if (sort === "newest") { 45 | setFilteredProducts((prev) => 46 | [...prev].sort((a, b) => a.createdAt - b.createdAt) 47 | ); 48 | } else if (sort === "asc") { 49 | setFilteredProducts((prev) => 50 | [...prev].sort((a, b) => a.price - b.price) 51 | ); 52 | } else { 53 | setFilteredProducts((prev) => 54 | [...prev].sort((a, b) => b.price - a.price) 55 | ); 56 | } 57 | }, [sort]); 58 | 59 | return ( 60 | 61 | {/* {filteredProducts.map((item) => ())} */} 62 | {cat 63 | ? filteredProducts.map((item) => ) 64 | : products 65 | .slice(0, 8) 66 | .map((item) => )} 67 | 68 | ); 69 | }; 70 | 71 | export default Products; -------------------------------------------------------------------------------- /shopitel-api/routes/product.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/Product"); 2 | const { 3 | verifyToken, 4 | verifyTokenAndAuthorization, 5 | verifyTokenAndAdmin, 6 | } = require("./verifyToken"); 7 | 8 | const router = require("express").Router(); 9 | 10 | //CREATE 11 | 12 | router.post("/", verifyTokenAndAdmin, async (req, res) => { 13 | const newProduct = new Product(req.body); 14 | 15 | try { 16 | const savedProduct = await newProduct.save(); 17 | res.status(200).json(savedProduct); 18 | } catch (err) { 19 | res.status(500).json(err); 20 | } 21 | }); 22 | 23 | //UPDATE 24 | router.put("/:id", verifyTokenAndAdmin, async (req, res) => { 25 | try { 26 | const updatedProduct = await Product.findByIdAndUpdate( 27 | req.params.id, 28 | { 29 | $set: req.body, 30 | }, 31 | { new: true } 32 | ); 33 | res.status(200).json(updatedProduct); 34 | } catch (err) { 35 | res.status(500).json(err); 36 | } 37 | }); 38 | 39 | //DELETE 40 | router.delete("/:id", verifyTokenAndAdmin, async (req, res) => { 41 | try { 42 | await Product.findByIdAndDelete(req.params.id); 43 | res.status(200).json("Product has been deleted..."); 44 | } catch (err) { 45 | res.status(500).json(err); 46 | } 47 | }); 48 | 49 | //GET PRODUCT 50 | router.get("/find/:id", async (req, res) => { 51 | try { 52 | const product = await Product.findById(req.params.id); 53 | res.status(200).json(product); 54 | } catch (err) { 55 | res.status(500).json(err); 56 | } 57 | }); 58 | 59 | //GET ALL PRODUCTS 60 | router.get("/", async (req, res) => { 61 | const qNew = req.query.new; 62 | const qCategory = req.query.category; 63 | try { 64 | let products; 65 | 66 | if (qNew) { 67 | products = await Product.find().sort({ createdAt: -1 }).limit(1); 68 | } else if (qCategory) { 69 | products = await Product.find({ 70 | categories: { 71 | $in: [qCategory], 72 | }, 73 | }); 74 | } else { 75 | products = await Product.find(); 76 | } 77 | 78 | res.status(200).json(products); 79 | } catch (err) { 80 | res.status(500).json(err); 81 | } 82 | }); 83 | 84 | module.exports = router; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🛍️ Shopitel 🛒

2 | 3 | https://user-images.githubusercontent.com/70844041/169762271-d50fef91-0664-4b17-a4a8-bfe54f9cdfe0.mp4 4 | 5 | ### To Get Started ⚡ 6 | 7 | 1. Clone the `shopitel-ecommerce-app` repository using git: 8 | 9 | ```bash 10 | git clone git@github.com:AbihaFatima/shopitel-ecommerce-app.git 11 | 12 | cd shopitel-api 13 | ``` 14 | 15 | 2. Install dependencies with this command: 16 | 17 | ```bash 18 | yarn add 19 | ``` 20 | 21 | or if you are using npm package manager: 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | 3. Run the backend application with this command: 28 | 29 | ```bash 30 | yarn start 31 | ``` 32 | 33 | now the node server will be running at `http://localhost:3000/` (for server side development) 34 | 35 | 4. To connect to the react client side : 36 | 37 | ```bash 38 | cd shopitel-client-react-ui 39 | ``` 40 | 41 | 5. Install dependencies (required for the front-end) with this command: 42 | 43 | ```bash 44 | yarn add 45 | ``` 46 | 47 | 6. Run the application with this command: 48 | 49 | ```bash 50 | yarn start 51 | ``` 52 | 53 | #### We're finally all set to go🎉 54 | 55 | The web application will be running at `http://localhost:3001/` 56 | 57 | #### Happy Shopping! 🛍️ 58 | 59 | ### Tech Stack Used: 60 | 61 | #### Backend 62 | 63 | - Express.js 64 | - Node.js 65 | - MongoDB 66 | - Mongoose 67 | - JWT (for authentication) 68 | - CryptoJS (for data encryption) 69 | - Stripe API (for checkout 💳) 70 | 71 | #### Frontend 72 | 73 | - React 74 | - Redux (to manage app state) 75 | - Redux Persist (for local storage) 76 | - React-router-dom (to handle routing) 77 | - Axios (for http requests) 78 | - Material-UI and styled-components (for component-level design) 79 | 80 | #### Future Plans 🚀 81 | 82 | - [ ] To add an Admin Dashboard that would support CRUD operations for new products as well as create charts for user analytics, cost and total revenue. 83 | - [ ] To make this application mobile responsive. 84 | - [ ] And last but not the least is to use a hosting service like Heroku to deploy this application. 85 | 86 | --- 87 | 88 | ```javascript 89 | if (youEnjoyed) { 90 | starThisRepository(⭐); 91 | } 92 | ``` 93 | --- 94 | #### Status 🛠️ 🚧 95 | This project is _in progress_ 96 | 97 | 98 | -------------------------------------------------------------------------------- /shopitel-api/routes/auth.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const User = require("../models/User") 3 | const CryptoJS = require("crypto-js") 4 | const jwt = require("jsonwebtoken"); 5 | const cookieParser = require('cookie-parser'); 6 | const session = require('express-session'); 7 | const passport = require("passport"); 8 | //Register 9 | router.post("/register", async (req,res) => { 10 | const newUser = new User({ 11 | username: req.body.username, 12 | email: req.body.email, 13 | password: CryptoJS.AES.encrypt(req.body.password,process.env.PASS_SEC).toString(), 14 | }); 15 | try 16 | { 17 | const savedUser = await newUser.save() 18 | res.status(201).json(savedUser); 19 | }catch(err){ 20 | res.status(500).json(err); 21 | } 22 | }); 23 | 24 | //Login 25 | router.post("/login", async(req, res)=>{ 26 | try{ 27 | const user = await User.findOne({username : req.body.username}); 28 | 29 | !user && res.status(401).json("Wrong User Name"); 30 | const hashedPassword = CryptoJS.AES.decrypt(user.password, process.env.PASS_SEC); 31 | 32 | const originalPassword = hashedPassword.toString(CryptoJS.enc.Utf8); 33 | 34 | const inputPassword = req.body.password; 35 | 36 | originalPassword != inputPassword && 37 | res.status(401).json("Wrong Password"); 38 | 39 | //Creating json web token 40 | const accessToken = jwt.sign( 41 | { 42 | id: user._id, 43 | isAdmin: user.isAdmin, 44 | }, 45 | process.env.JWT_SEC, 46 | {expiresIn:"3d"} 47 | ); 48 | 49 | const { password, ...others } = user._doc; 50 | res.status(200).json({...others, accessToken}); 51 | }catch(err){ 52 | res.status(500).json(err) 53 | } 54 | }); 55 | 56 | router.delete('/logout', (req, res) => { 57 | if (req.session) { 58 | req.session.destroy(err => { 59 | if (err) { 60 | res.status(400).send('Unable to log out') 61 | } else { 62 | res.send('Logout successful') 63 | } 64 | }); 65 | } else { 66 | res.end() 67 | } 68 | }); 69 | 70 | 71 | module.exports = router -------------------------------------------------------------------------------- /shopitel-api/routes/order.js: -------------------------------------------------------------------------------- 1 | const Order = require("../models/Order"); 2 | const { 3 | verifyToken, 4 | verifyTokenAndAuthorization, 5 | verifyTokenAndAdmin, 6 | } = require("./verifyToken"); 7 | 8 | const router = require("express").Router(); 9 | 10 | //CREATE 11 | 12 | router.post("/", verifyToken, async (req, res) => { 13 | const newOrder = new Order(req.body); 14 | 15 | try { 16 | const savedOrder = await newOrder.save(); 17 | res.status(200).json(savedOrder); 18 | } catch (err) { 19 | res.status(500).json(err); 20 | } 21 | }); 22 | 23 | //UPDATE 24 | router.put("/:id", verifyTokenAndAdmin, async (req, res) => { 25 | try { 26 | const updatedOrder = await Order.findByIdAndUpdate( 27 | req.params.id, 28 | { 29 | $set: req.body, 30 | }, 31 | { new: true } 32 | ); 33 | res.status(200).json(updatedOrder); 34 | } catch (err) { 35 | res.status(500).json(err); 36 | } 37 | }); 38 | 39 | //DELETE 40 | router.delete("/:id", verifyTokenAndAdmin, async (req, res) => { 41 | try { 42 | await Order.findByIdAndDelete(req.params.id); 43 | res.status(200).json("Order has been deleted..."); 44 | } catch (err) { 45 | res.status(500).json(err); 46 | } 47 | }); 48 | 49 | //GET USER ORDERS 50 | router.get("/find/:userId", verifyTokenAndAuthorization, async (req, res) => { 51 | try { 52 | const orders = await Order.find({ userId: req.params.userId }); 53 | res.status(200).json(orders); 54 | } catch (err) { 55 | res.status(500).json(err); 56 | } 57 | }); 58 | 59 | // //GET ALL 60 | 61 | router.get("/", verifyTokenAndAdmin, async (req, res) => { 62 | try { 63 | const orders = await Order.find(); 64 | res.status(200).json(orders); 65 | } catch (err) { 66 | res.status(500).json(err); 67 | } 68 | }); 69 | 70 | // GET MONTHLY INCOME 71 | 72 | router.get("/income", verifyTokenAndAdmin, async (req, res) => { 73 | const date = new Date(); 74 | const lastMonth = new Date(date.setMonth(date.getMonth() - 1)); 75 | const previousMonth = new Date(new Date().setMonth(lastMonth.getMonth() - 1)); 76 | 77 | try { 78 | const income = await Order.aggregate([ 79 | { $match: { createdAt: { $gte: previousMonth } } }, 80 | { 81 | $project: { 82 | month: { $month: "$createdAt" }, 83 | sales: "$amount", 84 | }, 85 | }, 86 | { 87 | $group: { 88 | _id: "$month", 89 | total: { $sum: "$sales" }, 90 | }, 91 | }, 92 | ]); 93 | res.status(200).json(income); 94 | } catch (err) { 95 | res.status(500).json(err); 96 | } 97 | }); 98 | 99 | module.exports = router; -------------------------------------------------------------------------------- /shopitel-api/routes/user.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const router = require("express").Router(); 3 | const { 4 | verifyToken, 5 | verifyTokenAndAuthorization, 6 | verifyTokenAndAdmin, 7 | } = require("./verifyToken"); 8 | 9 | //For Updating 10 | router.put("/:id", verifyTokenAndAuthorization, async(req,res) => { 11 | if (req.body.password) { 12 | req.body.password = CryptoJS.AES.encrypt( 13 | req.body.password, 14 | process.env.PASS_SEC 15 | ).toString(); 16 | } 17 | 18 | try { 19 | const updatedUser = await User.findByIdAndUpdate( 20 | req.params.id, 21 | { 22 | $set: req.body, 23 | }, 24 | { new: true } 25 | ); 26 | res.status(200).json(updatedUser); 27 | } catch (err) { 28 | res.status(500).json(err); 29 | } 30 | }); 31 | 32 | //DELETE 33 | router.delete("/:id", verifyTokenAndAuthorization, async (req, res) => { 34 | try { 35 | await User.findByIdAndDelete(req.params.id); 36 | res.status(200).json("User has been deleted..."); 37 | } catch (err) { 38 | res.status(500).json(err); 39 | } 40 | }); 41 | 42 | //GET USER 43 | router.get("/find/:id", verifyTokenAndAdmin, async (req, res) => { 44 | try { 45 | const user = await User.findById(req.params.id); 46 | const { password, ...others } = user._doc; 47 | res.status(200).json(others); 48 | } catch (err) { 49 | res.status(500).json(err); 50 | } 51 | }); 52 | 53 | //GET ALL USER 54 | router.get("/", verifyTokenAndAdmin, async (req, res) => { 55 | const query = req.query.new; 56 | try { 57 | const users = query 58 | ? await User.find().sort({ _id: -1 }).limit(5) 59 | : await User.find(); 60 | res.status(200).json(users); 61 | } catch (err) { 62 | res.status(500).json(err); 63 | } 64 | }); 65 | 66 | //GET USER STATS 67 | 68 | router.get("/stats", verifyTokenAndAdmin, async (req, res) => { 69 | const date = new Date(); 70 | const lastYear = new Date(date.setFullYear(date.getFullYear() - 1)); 71 | 72 | try { 73 | const data = await User.aggregate([ 74 | { $match: { createdAt: { $gte: lastYear } } }, 75 | { 76 | $project: { 77 | month: { $month: "$createdAt" }, 78 | }, 79 | }, 80 | { 81 | $group: { 82 | _id: "$month", 83 | total: { $sum: 1 }, 84 | }, 85 | }, 86 | ]); 87 | res.status(200).json(data) 88 | } catch (err) { 89 | res.status(500).json(err); 90 | } 91 | }); 92 | 93 | module.exports = router -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { login } from "../redux/apiCalls"; 3 | import { useState } from "react"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { Link } from "react-router-dom"; 6 | const Container = styled.div` 7 | width: 100vw; 8 | height: 100vh; 9 | background: linear-gradient( 10 | rgba(255, 255, 255, 0.5), 11 | rgba(255, 255, 255, 0.5) 12 | ), 13 | url("https://www.bdcmagazine.com/files/uploads/2020/10/fj300705670.jpg") 14 | center; 15 | background-size: cover; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | `; 20 | 21 | const Wrapper = styled.div` 22 | width: 25%; 23 | padding: 20px; 24 | background-color: white; 25 | `; 26 | 27 | const Title = styled.h1` 28 | font-size: 24px; 29 | font-weight: 300; 30 | `; 31 | 32 | const Error = styled.span` 33 | color: red; 34 | `; 35 | 36 | const Form = styled.form` 37 | display: flex; 38 | flex-direction: column; 39 | `; 40 | 41 | const Input = styled.input` 42 | flex: 1; 43 | min-width: 40%; 44 | margin: 10px 0; 45 | padding: 10px; 46 | `; 47 | 48 | const Button = styled.button` 49 | width: 40%; 50 | border: none; 51 | padding: 15px 20px; 52 | background-color: teal; 53 | color: white; 54 | cursor: pointer; 55 | margin-bottom: 10px; 56 | &:disabled { 57 | color: green; 58 | cursor: not-allowed; 59 | } 60 | `; 61 | 62 | 63 | const Login = () => { 64 | const [username, setUsername] = useState(""); 65 | const [password, setPassword] = useState(""); 66 | const dispatch = useDispatch(); 67 | const { isFetching, error } = useSelector((state) => state.user); 68 | 69 | const handleClick = (e) => { 70 | e.preventDefault(); 71 | login(dispatch, { username, password }); 72 | alert("Successfully Logged In as " + username); 73 | }; 74 | return ( 75 | 76 | 77 | SIGN IN 78 |
79 | setUsername(e.target.value)} 82 | /> 83 | setPassword(e.target.value)} 87 | /> 88 | 91 | {error && Something went wrong...} 92 | FORGOT PASSWORD? 93 | CREATE A NEW ACCOUNT 94 |
95 |
96 |
97 | ); 98 | }; 99 | 100 | export default Login; -------------------------------------------------------------------------------- /shopitel-client-react-ui/src/pages/ProductList.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import Navbar from "../components/Navbar"; 3 | import Announcement from "../components/Announcement"; 4 | import Products from "../components/Products"; 5 | import Newsletter from "../components/Newsletter"; 6 | import Footer from "../components/Footer"; 7 | import { useLocation } from "react-router"; 8 | import { useState } from "react"; 9 | 10 | const Container = styled.div``; 11 | 12 | const Title = styled.h1` 13 | margin: 20px; 14 | `; 15 | 16 | const FilterContainer = styled.div` 17 | display: flex; 18 | justify-content: space-between; 19 | `; 20 | 21 | const Filter = styled.div` 22 | margin: 20px; 23 | `; 24 | 25 | const FilterText = styled.span` 26 | font-size: 20px; 27 | font-weight: 600; 28 | margin-right: 20px; 29 | `; 30 | 31 | const Select = styled.select` 32 | padding: 10px; 33 | margin-right: 20px; 34 | `; 35 | const Option = styled.option``; 36 | 37 | const ProductList = () => { 38 | const location = useLocation(); 39 | const cat = location.pathname.split("/")[2]; 40 | const [filters, setFilters] = useState({}); 41 | const [sort, setSort] = useState("newest"); 42 | 43 | const handleFilters = (e) => { 44 | const value = e.target.value; 45 | setFilters({ 46 | ...filters, 47 | [e.target.name]: value, 48 | }); 49 | }; 50 | return ( 51 | 52 | 53 | 54 | {cat} 55 | 56 | 57 | Filter Products: 58 | 69 | 79 | 80 | 81 | Sort Products: 82 | 87 | 88 | 89 | 90 | 91 |