├── .gitignore ├── server ├── .gitignore ├── .ENV ├── .docker.env ├── views │ └── error.ejs ├── .dockerignore ├── .idea │ ├── jsLibraryMappings.xml │ ├── vcs.xml │ ├── modules.xml │ ├── app1.iml │ ├── runConfigurations │ │ └── bin_www.xml │ └── workspace.xml ├── Dockerfile ├── model │ ├── initdb.js │ └── User.js ├── utils │ └── convert.js ├── routes │ └── api │ │ ├── transaction.js │ │ ├── admin.js │ │ └── users.js ├── validation │ ├── login.js │ └── register.js ├── config │ └── passport.js ├── package.json ├── test │ └── users.test.js ├── utils.js ├── app.js └── bin │ └── www ├── client ├── public │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── robots.txt │ ├── assets │ │ └── img │ │ │ ├── search.svg │ │ │ ├── home.svg │ │ │ └── settings.svg │ ├── manifest.json │ ├── js │ │ ├── scrolling-nav.js │ │ ├── script.js │ │ ├── main.js │ │ ├── jquery.easing.min.js │ │ ├── jquery.nav.js │ │ ├── nivo-lightbox.js │ │ └── popper.min.js │ ├── css │ │ ├── styles.css │ │ ├── owl.theme.css │ │ ├── owl.carousel.css │ │ ├── main.map │ │ ├── nivo-lightbox.css │ │ ├── magnific-popup.css │ │ └── responsive.css │ └── index.html ├── src │ ├── components │ │ ├── App │ │ │ ├── App.css │ │ │ └── App.js │ │ ├── Admin │ │ │ └── Admin.js │ │ ├── Layout │ │ │ ├── Layout.js │ │ │ ├── Navbar.js │ │ │ ├── SideBar.js │ │ │ ├── Landing.js │ │ │ ├── AppView.js │ │ │ ├── header.css │ │ │ ├── Header.js │ │ │ └── layout.css │ │ ├── PrivateRoute │ │ │ └── PrivateRoute.js │ │ ├── Dashboard │ │ │ └── Dashboard.js │ │ ├── UserTable │ │ │ └── UserTable.js │ │ ├── Auth │ │ │ ├── login.css │ │ │ ├── login.js │ │ │ └── register.js │ │ └── InfoUser │ │ │ └── InfoUser.js │ ├── actions │ │ ├── types.js │ │ └── authActions.js │ ├── reducers │ │ ├── index.js │ │ ├── errorReducer.js │ │ └── authReducer.js │ ├── setupTests.js │ ├── App.test.js │ ├── utils │ │ └── setAuthToken.js │ ├── reportWebVitals.js │ ├── index.js │ ├── routes │ │ ├── PublicRoute.js │ │ └── PrivateRoute.js │ ├── store.js │ ├── index.css │ ├── assets │ │ └── logo.svg │ └── lib │ │ └── bootstrap │ │ ├── bootstrap-reboot.min.css │ │ └── bootstrap-reboot.css ├── .dockerignore ├── Dockerfile ├── .gitignore ├── package.json └── README.md ├── test └── example.test.js ├── docker-compose-prod.yml ├── README.md ├── package.json ├── docker-compose.yml └── docker-compose-dev.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npv2k1/MERN/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npv2k1/MERN/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npv2k1/MERN/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/src/components/App/App.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root, 4 | .App { 5 | height: 100%; 6 | } -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/.ENV: -------------------------------------------------------------------------------- 1 | JWT_SECRET=ABCDEF$123 2 | MONGO_HOSTNAME=localhost 3 | MONGO_DB=ndata 4 | MONGO_PORT=27017 -------------------------------------------------------------------------------- /server/.docker.env: -------------------------------------------------------------------------------- 1 | JWT_SECRET=ABCDEF$123 2 | MONGO_HOSTNAME=mongo 3 | MONGO_DB=ndata 4 | MONGO_PORT=27017 -------------------------------------------------------------------------------- /server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | upload 3 | export 4 | converage 5 | .git 6 | .tmp 7 | .vscode 8 | .github 9 | .env -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | upload 3 | export 4 | converage 5 | .git 6 | .tmp 7 | .vscode 8 | .github 9 | .env -------------------------------------------------------------------------------- /client/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const GET_ERRORS = "GET_ERRORS"; 2 | export const USER_LOADING = "USER_LOADING"; 3 | export const SET_CURRENT_USER = "SET_CURRENT_USER"; 4 | export const USER_UPDATE = "USER_UPDATE"; 5 | -------------------------------------------------------------------------------- /server/.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import authReducer from "./authReducer"; 3 | import errorReducer from "./errorReducer"; 4 | 5 | export default combineReducers({ 6 | auth: authReducer, 7 | errors: errorReducer 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /server/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /server/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/components/Admin/Admin.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Layout from "../Layout/Layout"; 3 | class App extends Component { 4 | render() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | } 12 | export default App; 13 | -------------------------------------------------------------------------------- /client/src/reducers/errorReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_ERRORS } from "../actions/types"; 2 | 3 | const initialState = {}; 4 | 5 | export default function(state = initialState, action) { 6 | switch (action.type) { 7 | case GET_ERRORS: 8 | return action.payload; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for client 2 | 3 | # Stage 1: Build react client 4 | FROM node:latest 5 | 6 | # Working directory be app 7 | WORKDIR /usr/app 8 | 9 | COPY package*.json ./ 10 | 11 | # Install dependencies 12 | RUN npm install 13 | 14 | # copy local files to app folder 15 | COPY . . 16 | 17 | EXPOSE 3000 18 | 19 | CMD ["npm","start"] -------------------------------------------------------------------------------- /client/src/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Layout from "../Layout/Layout" 3 | import "./../../lib/bootstrap/bootstrap.css"; 4 | class App extends Component { 5 | render() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | } 13 | export default App; 14 | -------------------------------------------------------------------------------- /client/public/assets/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/utils/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const setAuthToken = token => { 4 | if (token) { 5 | // Apply authorization token to every request if logged in 6 | axios.defaults.headers.common["Authorization"] = token; 7 | } else { 8 | // Delete auth header 9 | delete axios.defaults.headers.common["Authorization"]; 10 | } 11 | }; 12 | 13 | export default setAuthToken; 14 | -------------------------------------------------------------------------------- /client/.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 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for Node Express Backend api (development) 2 | 3 | FROM node:latest 4 | 5 | ARG NODE_ENV=development 6 | 7 | # Create App Directory 8 | RUN mkdir -p /usr/src/app 9 | WORKDIR /usr/src/app 10 | 11 | # Install Dependencies 12 | COPY package*.json ./ 13 | 14 | RUN npm install 15 | 16 | # Copy app source code 17 | COPY . . 18 | 19 | # Exports 20 | EXPOSE 8080 21 | 22 | CMD ["npm","start"] 23 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./components/App/App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { Provider } from "react-redux"; 7 | 8 | import store from "./store"; 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById("root") 16 | ); 17 | 18 | reportWebVitals(); 19 | -------------------------------------------------------------------------------- /server/.idea/app1.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /server/.idea/runConfigurations/bin_www.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/example.test.js: -------------------------------------------------------------------------------- 1 | // const assert = require("assert"); 2 | // describe("Simple Math Test", () => { 3 | // it("should return 2", () => { 4 | // assert.equal(1 + 1, 2); 5 | // }); 6 | // it("should return 9", () => { 7 | // assert.equal(3 * 3, 9); 8 | // }); 9 | // }); 10 | const expect = require("chai").expect; 11 | describe("Simple Math Test", () => { 12 | it("should return 2", () => { 13 | expect(1 + 1).to.equal(2); 14 | }); 15 | it("should return 9", () => { 16 | expect(3 * 3).to.equal(9); 17 | }); 18 | }); -------------------------------------------------------------------------------- /client/src/routes/PublicRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | 4 | // handle the public routes 5 | function PublicRoute({ component: Component, ...rest }) { 6 | return ( 7 | 10 | !rest.isAuthenticated ? ( 11 | 12 | ) : ( 13 | 14 | ) 15 | } 16 | /> 17 | ); 18 | } 19 | 20 | export default PublicRoute; 21 | -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from "redux"; 2 | import thunk from "redux-thunk"; 3 | import rootReducer from "./reducers"; 4 | 5 | const initialState = {}; 6 | 7 | const middleware = [thunk]; 8 | 9 | const store = createStore( 10 | rootReducer, 11 | initialState, 12 | compose( 13 | applyMiddleware(...middleware), 14 | (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && 15 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__()) || 16 | compose 17 | ) 18 | ); 19 | 20 | export default store; 21 | -------------------------------------------------------------------------------- /client/src/routes/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | 4 | // handle the private routes 5 | function PrivateRoute({ component: Component, ...rest }) { 6 | return ( 7 | 10 | rest.isAuthenticated ? ( 11 | 12 | ) : ( 13 | 16 | ) 17 | } 18 | /> 19 | ); 20 | } 21 | 22 | export default PrivateRoute; 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/model/initdb.js: -------------------------------------------------------------------------------- 1 | 2 | const mongoose = require("mongoose"); 3 | 4 | require("dotenv").config(); 5 | 6 | // mongoose options 7 | const options = { 8 | useUnifiedTopology: true, 9 | useNewUrlParser: true, 10 | useCreateIndex: true, 11 | }; 12 | 13 | // mongodb environment variables 14 | const { MONGO_HOSTNAME, MONGO_DB, MONGO_PORT } = process.env; 15 | 16 | const dbConnectionURL = { 17 | LOCAL_DB_URL: `mongodb://${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}`, 18 | REMOTE_DB_URL: process.env.MONGODB_URI, //atlas url 19 | }; 20 | mongoose.connect(dbConnectionURL.LOCAL_DB_URL, options); 21 | module.exports = { mongoose }; -------------------------------------------------------------------------------- /client/src/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Header from "./Header" 4 | import SideBar from "./SideBar" 5 | import AppView from "./AppView" 6 | 7 | 8 | import NewHeader from "./Header" 9 | class Layout extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { item: [] }; 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 | 19 |
20 | 21 | 22 |
23 |
24 | ); 25 | } 26 | } 27 | export default Layout; 28 | -------------------------------------------------------------------------------- /docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | env_file: ./server/.env 10 | environment: 11 | NODE_ENV: production 12 | depends_on: 13 | - mongo 14 | networks: 15 | - app-network 16 | mongo: 17 | image: mongo 18 | volumes: 19 | - data-volume:/data/db 20 | ports: 21 | - "27017:27017" 22 | networks: 23 | - app-network 24 | 25 | networks: 26 | app-network: 27 | driver: bridge 28 | 29 | volumes: 30 | data-volume: 31 | node_modules: 32 | web-root: 33 | driver: local -------------------------------------------------------------------------------- /client/src/components/PrivateRoute/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | 6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => ( 7 | 10 | auth.isAuthenticated === true ? () : () 11 | } 12 | /> 13 | ); 14 | 15 | PrivateRoute.propTypes = { 16 | auth: PropTypes.object.isRequired 17 | }; 18 | 19 | const mapStateToProps = state => ({auth: state.auth}); 20 | 21 | export default connect(mapStateToProps)(PrivateRoute); 22 | -------------------------------------------------------------------------------- /server/utils/convert.js: -------------------------------------------------------------------------------- 1 | module.exports ={ 2 | getISODate:function (string) { 3 | let re = /(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})/; 4 | let res = re.exec(string); 5 | if (!res) { 6 | console.log(res.input + "ERROR"); 7 | } 8 | else { 9 | let x = res[0].split(/\/|-|\./gi); 10 | // console.log(x) 11 | return new Date(parseInt(x[2]), parseInt(x[1]) - 1, parseInt(x[0])).toISOString() 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /client/src/components/Layout/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | class Navbar extends Component { 5 | render() { 6 | return ( 7 |
8 | 22 |
23 | ); 24 | } 25 | } 26 | 27 | export default Navbar; 28 | -------------------------------------------------------------------------------- /client/src/reducers/authReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_CURRENT_USER, USER_LOADING } from "../actions/types"; 2 | 3 | const isEmpty = require("is-empty"); 4 | 5 | // Khởi tạo state mặc định 6 | const initialState = { 7 | isAuthenticated: false, 8 | user: {}, 9 | loading: false, 10 | }; 11 | 12 | // eslint-disable-next-line import/no-anonymous-default-export 13 | export default function (state = initialState, action) { 14 | switch (action.type) { 15 | case SET_CURRENT_USER: 16 | return { 17 | ...state, 18 | isAuthenticated: !isEmpty(action.payload), 19 | user: action.payload, 20 | }; 21 | case USER_LOADING: 22 | return { 23 | ...state, 24 | loading: true, 25 | }; 26 | default: 27 | return state; 28 | } 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MERN 2 | 3 | ## Client 4 | 5 | ```powershell 6 | npx create-react-app client 7 | ``` 8 | 9 | Start 10 | 11 | ```powershell 12 | npm start 13 | ``` 14 | 15 | conect server nodejs 16 | 17 | ```json 18 | { 19 | ... 20 | "proxy": "http://localhost:5000/", 21 | ... 22 | } 23 | 24 | ``` 25 | 26 | ## Server 27 | port 28 | 29 | ```javascript 30 | var port = normalizePort(process.env.PORT || "5000"); 31 | ``` 32 | 33 | # MERN 34 | 35 | start mern 36 | 37 | Build client: 38 | 39 | ```powershell 40 | cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build 41 | ``` 42 | 43 | React build: 44 | 45 | ```javascript 46 | app.use(express.static("./../client/build")); 47 | ``` 48 | 49 | Run server 50 | 51 | ```powershell 52 | cd server && npm start 53 | ``` 54 | -------------------------------------------------------------------------------- /server/routes/api/transaction.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const bcrypt = require("bcryptjs"); 4 | const jwt = require("jsonwebtoken"); 5 | const passport = require("passport"); 6 | var moment = require("moment"); 7 | 8 | const { getISODate } = require("./../../utils/convert"); 9 | 10 | require("dotenv").config(); 11 | // Load input validation 12 | const validateRegisterInput = require("../../validation/register"); 13 | const validateLoginInput = require("../../validation/login"); 14 | 15 | // Load User model 16 | const Transactions = require("../../model/transactionSchema"); 17 | 18 | router.post("/add",(req,res)=>{ 19 | Transactions.create({...req.body}).then(msg=>{ 20 | res.send(msg) 21 | }) 22 | }) 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /server/model/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("./initdb").mongoose; 2 | 3 | const usersSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true 7 | }, 8 | birthday:{ 9 | type: Date, 10 | }, 11 | gender:{ 12 | type:Boolean, 13 | default: true 14 | }, 15 | email: { 16 | type: String, 17 | required: true 18 | }, 19 | phone:{ 20 | type:Number, 21 | }, 22 | address:{ 23 | type:String, 24 | }, 25 | password: { 26 | type: String, 27 | required: true 28 | }, 29 | date: { 30 | type: Date, 31 | default: Date.now 32 | }, 33 | token:{ 34 | type:String 35 | }, 36 | role:{ 37 | type:Number, 38 | default: 2, 39 | } 40 | }); 41 | // tạo model 42 | const Users = mongoose.model("users", usersSchema); 43 | 44 | 45 | module.exports = Users 46 | -------------------------------------------------------------------------------- /server/validation/login.js: -------------------------------------------------------------------------------- 1 | const Validator = require("validator"); 2 | const isEmpty = require("is-empty"); 3 | 4 | module.exports = function validateLoginInput(data) { 5 | let errors = {}; 6 | 7 | // Convert empty fields to an empty string so we can use validator functions 8 | data.email = !isEmpty(data.email) ? data.email : ""; 9 | data.password = !isEmpty(data.password) ? data.password : ""; 10 | 11 | // Email checks 12 | if (Validator.isEmpty(data.email)) { 13 | errors.email = "Email field is required"; 14 | } else if (!Validator.isEmail(data.email)) { 15 | errors.email = "Email is invalid"; 16 | } 17 | // Password checks 18 | if (Validator.isEmpty(data.password)) { 19 | errors.password = "Password field is required"; 20 | } 21 | 22 | return { 23 | errors, 24 | isValid: isEmpty(errors) 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /server/config/passport.js: -------------------------------------------------------------------------------- 1 | const JwtStrategy = require("passport-jwt").Strategy; 2 | const ExtractJwt = require("passport-jwt").ExtractJwt; 3 | const mongoose = require("mongoose"); 4 | const User = require('./../model/User.js') 5 | 6 | require("dotenv").config() 7 | 8 | const opts = {}; 9 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 10 | opts.secretOrKey = process.env.JWT_SECRET; 11 | 12 | module.exports = passport => { 13 | passport.use( 14 | new JwtStrategy(opts, (jwt_payload, done) => { 15 | User.findById(jwt_payload.id) 16 | .then(user => { 17 | if (user) { 18 | return done(null, user); 19 | } 20 | return done(null, false); 21 | }) 22 | .catch(err => console.log(err)); 23 | }) 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /client/public/assets/img/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n-mern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "server": "cd server && npm start ", 9 | "client": "cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build", 10 | "start": "concurrently \"npm run server\" \"npm run client\"", 11 | "heroku-postbuild": "cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/npv2k1/MERN.git" 16 | }, 17 | "keywords": [], 18 | "author": "npv2k1", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/npv2k1/MERN/issues" 22 | }, 23 | "homepage": "https://github.com/npv2k1/MERN#readme", 24 | "dependencies": { 25 | "concurrently": "^5.3.0", 26 | "react-router-dom": "^5.2.0" 27 | }, 28 | "devDependencies": { 29 | "chai": "^4.3.0", 30 | "mocha": "^8.2.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/public/js/scrolling-nav.js: -------------------------------------------------------------------------------- 1 | //jQuery to collapse the navbar on scroll 2 | $(window).scroll(function() { 3 | if ($(".navbar").offset().top > 50) { 4 | $(".navbar-fixed-top").addClass("top-nav-collapse"); 5 | } else { 6 | $(".navbar-fixed-top").removeClass("top-nav-collapse"); 7 | } 8 | }); 9 | 10 | //jQuery for page scrolling feature - requires jQuery Easing plugin 11 | $(function() { 12 | 13 | $('a.page-scroll[href*="#"]:not([href="#"])').on('click', function () { 14 | if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { 15 | var target = $(this.hash); 16 | target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); 17 | if (target.length) { 18 | $('html, body').animate({ 19 | scrollTop: (target.offset().top -80) 20 | }, 1500, "easeInOutExpo"); 21 | return false; 22 | } 23 | } 24 | }); 25 | 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server-mern", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "dev": "nodemon ./bin/www", 8 | "test": "mocha" 9 | }, 10 | "dependencies": { 11 | "bcrypt": "^5.0.0", 12 | "bcryptjs": "^2.4.3", 13 | "body-parser": "^1.19.0", 14 | "concurrently": "^5.3.0", 15 | "connect-flash": "^0.1.1", 16 | "cookie-parser": "~1.4.4", 17 | "cors": "^2.8.5", 18 | "debug": "~2.6.9", 19 | "dotenv": "^8.2.0", 20 | "ejs": "~2.6.1", 21 | "express": "^4.16.4", 22 | "express-session": "^1.17.1", 23 | "http-errors": "~1.6.3", 24 | "is-empty": "^1.2.0", 25 | "jsonwebtoken": "^8.5.1", 26 | "moment": "^2.29.1", 27 | "mongoose": "^5.11.8", 28 | "morgan": "^1.9.1", 29 | "multer": "^1.4.2", 30 | "mysql": "^2.18.1", 31 | "passport": "^0.4.1", 32 | "passport-jwt": "^4.0.0", 33 | "passport-local": "^1.0.0", 34 | "sqlite3": "^5.0.0", 35 | "validator": "^13.5.2" 36 | }, 37 | "devDependencies": { 38 | "chai": "^4.3.0", 39 | "chai-http": "^4.3.0", 40 | "mocha": "^8.2.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/test/users.test.js: -------------------------------------------------------------------------------- 1 | //During the test the env variable is set to test 2 | process.env.NODE_ENV = "test"; 3 | 4 | //Require the dev-dependencies 5 | let chai = require("chai"); 6 | let chaiHttp = require("chai-http"); 7 | let server = require("./../bin/www"); 8 | let should = chai.should(); 9 | const expect = require("chai").expect; 10 | chai.use(chaiHttp); 11 | //Our parent block 12 | describe("Users", () => { 13 | beforeEach((done) => { 14 | //Before each test we empty the database in your case 15 | done(); 16 | }); 17 | /* 18 | * Test the /GET route 19 | */ 20 | describe("/GET listusers", () => { 21 | it("it should GET listusers", (done) => { 22 | chai 23 | .request(server) 24 | .get("/api/admin/listusers") 25 | .end((err, res) => { 26 | res.should.have.status(200); 27 | res.body.should.be.a("array"); 28 | // res.body.length.should.be.eql(9); // fixme :) 29 | done(); 30 | }); 31 | }); 32 | }); 33 | describe("Simple Math Test", () => { 34 | it("should return 2", () => { 35 | expect(1 + 1).to.equal(2); 36 | }); 37 | it("should return 9", () => { 38 | expect(3 * 3).to.equal(9); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /client/public/css/styles.css: -------------------------------------------------------------------------------- 1 | #evInput { 2 | width: max-content; 3 | } 4 | 5 | #evInput {} 6 | 7 | 8 | body { 9 | background: #74ebd5; 10 | background: -webkit-linear-gradient(to right, #74ebd5, #ACB6E5); 11 | background: linear-gradient(to right, #74ebd5, #ACB6E5); 12 | min-height: 100vh; 13 | } 14 | 15 | 16 | 17 | 18 | .header-fixed { 19 | width: 100% 20 | } 21 | 22 | .header-fixed>thead, 23 | .header-fixed>tbody, 24 | .header-fixed>thead>tr, 25 | .header-fixed>tbody>tr, 26 | .header-fixed>thead>tr>th, 27 | .header-fixed>tbody>tr>td { 28 | display: block; 29 | text-align: center; 30 | } 31 | 32 | .header-fixed>tbody>tr:after, 33 | .header-fixed>thead>tr:after { 34 | content: ' '; 35 | display: block; 36 | visibility: hidden; 37 | clear: both; 38 | } 39 | 40 | .header-fixed>tbody { 41 | overflow-y: auto; 42 | height: 70vh !important; 43 | } 44 | 45 | .header-fixed>tbody>tr>td, 46 | .header-fixed>thead>tr>th { 47 | width: 15%; 48 | float: left; 49 | } 50 | 51 | .header-fixed4>tbody>tr>td, 52 | .header-fixed4>thead>tr>th { 53 | width: 20% !important; 54 | float: left; 55 | } 56 | 57 | table { 58 | table-layout: fixed; 59 | word-wrap: break-word !important; 60 | } 61 | 62 | table td { 63 | overflow: hidden; 64 | } 65 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-mern", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "axios": "^0.21.1", 10 | "classnames": "^2.2.6", 11 | "is-empty": "^1.2.0", 12 | "jwt-decode": "^3.1.2", 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1", 15 | "react-redux": "^7.2.2", 16 | "react-router-dom": "^5.2.0", 17 | "react-scripts": "4.0.1", 18 | "redux": "^4.0.5", 19 | "redux-thunk": "^2.3.0", 20 | "web-vitals": "^0.2.4" 21 | }, 22 | "proxy": "http://localhost:5000/", 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/src/components/Layout/SideBar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./layout.css"; 3 | 4 | 5 | class ItemSideBar extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { s: 1 }; 9 | } 10 | render() { 11 | return ( 12 |
13 | 21 | {this.props.alt} 22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | class SideBar extends React.Component { 29 | constructor(props) { 30 | super(props); 31 | this.state = { current: 1 }; 32 | this.activeLink = this.activeLink.bind(this); 33 | } 34 | activeLink(e) { 35 | this.setState({ current: e }); 36 | } 37 | render() { 38 | return ( 39 |
40 | 41 | 46 |
47 | ); 48 | } 49 | } 50 | export default SideBar -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | server: 5 | build: 6 | context: ./server 7 | dockerfile: Dockerfile 8 | image: mern-server 9 | container_name: mern-node-server 10 | command: npm run start 11 | volumes: 12 | - ./server/:/usr/src/app 13 | - /usr/src/app/node_modules 14 | ports: 15 | - "5000:5000" 16 | depends_on: 17 | - mongo 18 | env_file: ./server/.docker.env 19 | environment: 20 | - NODE_ENV=development 21 | networks: 22 | - app-network 23 | mongo: 24 | image: mongo:latest 25 | container_name: mern-mongo-server 26 | volumes: 27 | - data-volume:/data/db 28 | ports: 29 | - "28017:27017" 30 | networks: 31 | - app-network 32 | client: 33 | build: 34 | context: ./client 35 | dockerfile: Dockerfile 36 | image: mern-client 37 | container_name: mern-react-client 38 | command: npm run start 39 | volumes: 40 | - ./client/:/usr/app 41 | - /usr/app/node_modules 42 | depends_on: 43 | - server 44 | ports: 45 | - "3000:3000" 46 | networks: 47 | - app-network 48 | 49 | networks: 50 | app-network: 51 | driver: bridge 52 | 53 | volumes: 54 | data-volume: 55 | node_modules: 56 | web-root: 57 | driver: local -------------------------------------------------------------------------------- /client/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 | body { 15 | margin: 0; 16 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 17 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 18 | sans-serif; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | code { 24 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 25 | monospace; 26 | } 27 | 28 | /* .content { 29 | padding: 20px; 30 | } 31 | 32 | .header { 33 | padding: 10px; 34 | background: #edf2f4; 35 | border-bottom: 1px solid #999; 36 | } 37 | 38 | .header a { 39 | color: #0072ff; 40 | text-decoration: none; 41 | margin-left: 20px; 42 | margin-right: 5px; 43 | } 44 | 45 | .header a:hover { 46 | color: #8a0f53; 47 | } 48 | 49 | .header small { 50 | color: #666; 51 | } 52 | 53 | .header .active { 54 | color: #2c7613; 55 | } */ 56 | html, 57 | body, 58 | #root, 59 | .App { 60 | height: 100%; 61 | } -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | server: 5 | build: 6 | context: ./server 7 | dockerfile: Dockerfile 8 | image: myapp-server 9 | container_name: myapp-node-server 10 | command: npm run start 11 | volumes: 12 | - ./server/:/usr/src/app 13 | - /usr/src/app/node_modules 14 | ports: 15 | - "8080:8080" 16 | depends_on: 17 | - mongo 18 | env_file: ./server/.env 19 | environment: 20 | - NODE_ENV=development 21 | networks: 22 | - app-network 23 | mongo: 24 | image: mongo:latest 25 | # environment: // If Mongo Authorization is need to be enabled 26 | # MONGO_INITDB_ROOT_USERNAME: root 27 | # MONGO_INITDB_ROOT_PASSWORD: rootpassword 28 | volumes: 29 | - data-volume:/data/db 30 | ports: 31 | - "27017:27017" 32 | networks: 33 | - app-network 34 | client: 35 | build: 36 | context: ./client 37 | dockerfile: Dockerfile 38 | image: myapp-client 39 | container_name: myapp-react-client 40 | command: npm run start 41 | volumes: 42 | - ./client/:/usr/app 43 | - /usr/app/node_modules 44 | depends_on: 45 | - server 46 | ports: 47 | - "3000:3000" 48 | networks: 49 | - app-network 50 | 51 | networks: 52 | app-network: 53 | driver: bridge 54 | 55 | volumes: 56 | data-volume: 57 | node_modules: 58 | web-root: 59 | driver: local -------------------------------------------------------------------------------- /server/validation/register.js: -------------------------------------------------------------------------------- 1 | const Validator = require("validator"); 2 | const isEmpty = require("is-empty"); 3 | 4 | module.exports = function validateRegisterInput(data) { 5 | let errors = {}; 6 | 7 | // Convert empty fields to an empty string so we can use validator functions 8 | data.name = !isEmpty(data.name) ? data.name : ""; 9 | // data.birthday= !isEmpty(data.birthday) ? data.birthday : ""; 10 | // data.gender = !isEmpty(data.gender)?data.gender:1; 11 | 12 | data.email = !isEmpty(data.email) ? data.email : ""; 13 | // data.phone = !isEmpty(data.phone)?data.phone:""; 14 | data.password = !isEmpty(data.password) ? data.password : ""; 15 | data.password2 = !isEmpty(data.password2) ? data.password2 : ""; 16 | 17 | // Name checks 18 | if (Validator.isEmpty(data.name)) { 19 | errors.name = "Name field is required"; 20 | } 21 | 22 | // Email checks 23 | if (Validator.isEmpty(data.email)) { 24 | errors.email = "Email field is required"; 25 | } else if (!Validator.isEmail(data.email)) { 26 | errors.email = "Email is invalid"; 27 | } 28 | 29 | // Password checks 30 | if (Validator.isEmpty(data.password)) { 31 | errors.password = "Password field is required"; 32 | } 33 | 34 | if (Validator.isEmpty(data.password2)) { 35 | errors.password2 = "Confirm password field is required"; 36 | } 37 | 38 | if (!Validator.isLength(data.password, { min: 6, max: 30 })) { 39 | errors.password = "Password must be at least 6 characters"; 40 | } 41 | 42 | if (!Validator.equals(data.password, data.password2)) { 43 | errors.password2 = "Passwords must match"; 44 | } 45 | 46 | return { 47 | errors, 48 | isValid: isEmpty(errors) 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /client/src/components/Dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import { logoutUser } from "../../actions/authActions"; 5 | 6 | class Dashboard extends Component { 7 | onLogoutClick = e => { 8 | e.preventDefault(); 9 | this.props.logoutUser(); 10 | }; 11 | 12 | render() { 13 | const { user } = this.props.auth; 14 | 15 | return ( 16 |
17 |
18 |
19 |

20 | Hey there, {user.name.split(" ")[0]} 21 |

22 | You are logged into a full-stack{" "} 23 | MERN app 👏 24 |

25 |

26 | 38 |
39 |
40 |
41 | ); 42 | } 43 | } 44 | Dashboard.propTypes = { 45 | logoutUser: PropTypes.func.isRequired, 46 | auth: PropTypes.object.isRequired 47 | }; 48 | 49 | const mapStateToProps = state => ({ 50 | auth: state.auth 51 | }); 52 | 53 | export default connect( 54 | mapStateToProps, 55 | { logoutUser } 56 | )(Dashboard); 57 | -------------------------------------------------------------------------------- /server/utils.js: -------------------------------------------------------------------------------- 1 | // generate token using secret from process.env.JWT_SECRET 2 | var jwt = require("jsonwebtoken"); 3 | 4 | // generate token and return it 5 | function generateToken(user) { 6 | //1. Don't use password and other sensitive fields 7 | //2. Use the information that are useful in other parts 8 | 9 | if (!user) return null; 10 | 11 | var u = { 12 | userId: user._id, 13 | firstname: user.firstname, 14 | lastname:user.lastname, 15 | email: user.email, 16 | phone:user.phone, 17 | username: user.username, 18 | role: user.role, 19 | }; 20 | 21 | return jwt.sign(u, process.env.JWT_SECRET, { 22 | expiresIn: 60 * 60 * 24, // expires in 24 hours 23 | }); 24 | } 25 | 26 | // return basic user details 27 | function getCleanUser(user) { 28 | if (!user) return null; 29 | 30 | return { 31 | userId: user._id, 32 | firstname: user.firstname, 33 | lastname: user.lastname, 34 | email: user.email, 35 | phone: user.phone, 36 | username: user.username, 37 | role: user.role, 38 | }; 39 | } 40 | function dateFormat(string) { 41 | let re = /(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})/; 42 | let res = re.exec(string); 43 | if (!res) { 44 | console.log(res.input + "ERROR"); 45 | } else { 46 | let x = res[0].split(/\/|-|\./gi); 47 | // console.log(x) 48 | return new Date( 49 | parseInt(x[2]), 50 | parseInt(x[1]) - 1, 51 | parseInt(x[0]) 52 | ).toISOString(); 53 | } 54 | } 55 | module.exports = { 56 | generateToken, 57 | getCleanUser, 58 | dateFormat, 59 | }; 60 | -------------------------------------------------------------------------------- /client/src/components/Layout/Landing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | class Landing extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |
10 |

11 | Build a login/auth app with the{" "} 12 | MERN stack from 13 | scratch 14 |

15 |

16 | Create a (minimal) full-stack app with user authentication via 17 | passport and JWTs 18 |

19 |
20 |
21 | 30 | Register 31 | 32 |
33 |
34 | 43 | Log In 44 | 45 |
46 |
47 |
48 |
49 | ); 50 | } 51 | } 52 | 53 | export default Landing; 54 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var createError = require("http-errors"); 2 | var express = require("express"); 3 | var path = require("path"); 4 | var cookieParser = require("cookie-parser"); 5 | var logger = require("morgan"); 6 | const bodyParser = require("body-parser"); 7 | const multer = require("multer"); 8 | const cors = require("cors"); 9 | const session = require("express-session"); 10 | const passport = require("passport"); 11 | const jwt = require("jsonwebtoken"); 12 | const flash = require("connect-flash"); 13 | 14 | require("./config/passport")(passport); 15 | require("dotenv").config(); 16 | 17 | 18 | 19 | 20 | var app = express(); 21 | // Production React 22 | 23 | // app.use(express.static("./../client/build")); 24 | 25 | // view engine setup 26 | app.use(cors()); 27 | 28 | 29 | app.use(passport.initialize()); 30 | 31 | 32 | app.set("views", path.join(__dirname, "views")); 33 | app.set("view engine", "ejs"); 34 | 35 | app.use(logger("dev")); 36 | app.use(bodyParser.json()); 37 | app.use(bodyParser.urlencoded({ extended: true })); 38 | app.use(multer().array()); 39 | 40 | app.use(cookieParser()); 41 | app.use(express.static(path.join(__dirname, "public"))); 42 | 43 | 44 | // Routes 45 | app.use("/api/users", require('./routes/api/users')); 46 | app.use("/api/admin", require("./routes/api/admin")); 47 | 48 | 49 | /* 50 | * Error 404 51 | */ 52 | 53 | // catch 404 and forward to error handler 54 | app.use(function (req, res, next) { 55 | next(createError(404)); 56 | }); 57 | 58 | // error handler 59 | app.use(function (err, req, res, next) { 60 | // set locals, only providing error in development 61 | res.locals.message = err.message; 62 | res.locals.error = req.app.get("env") === "development" ? err : {}; 63 | 64 | // render the error page 65 | res.status(err.status || 500); 66 | res.render("error"); 67 | }); 68 | 69 | module.exports = app; 70 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client/public/css/owl.theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl Carousel Owl Demo Theme 3 | * v1.3.3 4 | */ 5 | 6 | .owl-theme .owl-controls{ 7 | margin-top: 10px; 8 | text-align: center; 9 | } 10 | 11 | /* Styling Next and Prev buttons */ 12 | 13 | .owl-theme .owl-controls .owl-buttons div{ 14 | color: #FFF; 15 | display: inline-block; 16 | zoom: 1; 17 | *display: inline;/*IE7 life-saver */ 18 | margin: 5px; 19 | padding: 3px 10px; 20 | font-size: 12px; 21 | -webkit-border-radius: 30px; 22 | -moz-border-radius: 30px; 23 | border-radius: 30px; 24 | background: #869791; 25 | filter: Alpha(Opacity=50);/*IE7 fix*/ 26 | opacity: 0.5; 27 | } 28 | /* Clickable class fix problem with hover on touch devices */ 29 | /* Use it for non-touch hover action */ 30 | .owl-theme .owl-controls.clickable .owl-buttons div:hover{ 31 | filter: Alpha(Opacity=100);/*IE7 fix*/ 32 | opacity: 1; 33 | text-decoration: none; 34 | } 35 | 36 | /* Styling Pagination*/ 37 | 38 | .owl-theme .owl-controls .owl-page{ 39 | display: inline-block; 40 | zoom: 1; 41 | *display: inline;/*IE7 life-saver */ 42 | } 43 | .owl-theme .owl-controls .owl-page span{ 44 | display: block; 45 | width: 12px; 46 | height: 12px; 47 | margin: 5px 7px; 48 | filter: Alpha(Opacity=50);/*IE7 fix*/ 49 | opacity: 0.5; 50 | -webkit-border-radius: 20px; 51 | -moz-border-radius: 20px; 52 | border-radius: 20px; 53 | background: #869791; 54 | } 55 | 56 | .owl-theme .owl-controls .owl-page.active span, 57 | .owl-theme .owl-controls.clickable .owl-page:hover span{ 58 | filter: Alpha(Opacity=100);/*IE7 fix*/ 59 | opacity: 1; 60 | } 61 | 62 | /* If PaginationNumbers is true */ 63 | 64 | .owl-theme .owl-controls .owl-page span.owl-numbers{ 65 | height: auto; 66 | width: auto; 67 | color: #FFF; 68 | padding: 2px 10px; 69 | font-size: 12px; 70 | -webkit-border-radius: 30px; 71 | -moz-border-radius: 30px; 72 | border-radius: 30px; 73 | } 74 | 75 | /* preloading images */ 76 | .owl-item.loading{ 77 | min-height: 150px; 78 | background: url(AjaxLoader.gif) no-repeat center center 79 | } -------------------------------------------------------------------------------- /server/routes/api/admin.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const bcrypt = require("bcryptjs"); 4 | const jwt = require("jsonwebtoken"); 5 | const passport = require("passport"); 6 | var moment = require("moment"); 7 | 8 | const {getISODate }= require('./../../utils/convert') 9 | 10 | require("dotenv").config(); 11 | // Load input validation 12 | const validateRegisterInput = require("../../validation/register"); 13 | const validateLoginInput = require("../../validation/login"); 14 | 15 | // Load User model 16 | const User = require("../../model/User"); 17 | 18 | // TODO Check admin 19 | 20 | // get users list 21 | router.get('/listusers',(req,res)=>{ 22 | User.find({},{password:0}).then(data=>{ 23 | res.send(data) 24 | }) 25 | }) 26 | 27 | router.post('/create', (req,res)=>{ 28 | const { errors, isValid } = validateRegisterInput(req.body); 29 | // Check validation 30 | if (!isValid) { 31 | return res.status(400).json(errors); 32 | } 33 | const{name,birthday,gender,email,phone,address,password,role} = req.body 34 | User.findOne({ email: req.body.email }).then((user) => { 35 | if (user) { 36 | return res.status(400).json({ email: "Email already exists" }); 37 | } else { 38 | const newUser = new User({ 39 | name: name, 40 | birthday:moment(birthday).format('DD/MM/YYYY'), 41 | gender:gender, 42 | email:email, 43 | phone:phone, 44 | address:address, 45 | password:password, 46 | role:role 47 | }); 48 | 49 | // Hash password before saving in database 50 | bcrypt.genSalt(10, (err, salt) => { 51 | bcrypt.hash(newUser.password, salt, (err, hash) => { 52 | if (err) throw err; 53 | newUser.password = hash; 54 | newUser 55 | .save() 56 | .then((user) => res.json(user)) 57 | .catch((err) => console.log(err)); 58 | }); 59 | }); 60 | } 61 | }); 62 | }) 63 | 64 | module.exports = router; 65 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('app1:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '5000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | 92 | 93 | module.exports = server; // for testing 94 | -------------------------------------------------------------------------------- /client/src/components/Layout/AppView.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 4 | import { Provider } from "react-redux"; 5 | import jwt_decode from "jwt-decode"; 6 | 7 | import "./layout.css"; 8 | 9 | import Login from "../Auth/login"; 10 | import Register from "../Auth/register"; 11 | import UserTable from "../UserTable/UserTable"; 12 | import InfoUser from "../InfoUser/InfoUser"; 13 | 14 | import setAuthToken from "../../utils/setAuthToken"; 15 | 16 | import { setCurrentUser, logoutUser } from "../../actions/authActions"; 17 | 18 | import store from "../../store"; 19 | 20 | import PrivateRoute from "../PrivateRoute/PrivateRoute"; 21 | 22 | import Dashboard from "../Dashboard/Dashboard"; 23 | 24 | // Check for token to keep user logged in 25 | if (localStorage.jwtToken) { 26 | // Set auth token header auth 27 | const token = localStorage.jwtToken; 28 | setAuthToken(token); 29 | // Decode token and get user info and exp 30 | const decoded = jwt_decode(token); 31 | // Set user and isAuthenticated 32 | store.dispatch(setCurrentUser(decoded)); 33 | // Check for expired token 34 | const currentTime = Date.now() / 1000; // to get in milliseconds 35 | if (decoded.exp < currentTime) { 36 | // Logout user 37 | store.dispatch(logoutUser()); 38 | 39 | // Redirect to login 40 | window.location.href = "./login"; 41 | } 42 | } 43 | class AppView extends React.Component { 44 | render() { 45 | return ( 46 |
47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 |
59 | ); 60 | } 61 | } 62 | export default AppView; 63 | -------------------------------------------------------------------------------- /client/src/components/UserTable/UserTable.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import "./../../lib/bootstrap/bootstrap.css" 3 | import axios from "axios" 4 | const fs = require('fs') 5 | class UserTable extends Component { 6 | constructor(){ 7 | super(); 8 | this.state={userList:[],loading:true} 9 | } 10 | componentDidMount(){ 11 | axios.get('/api/admin/listusers').then(res=>{ 12 | this.setState({userList:res.data}) 13 | console.log(this.state.userList) 14 | this.setState({loading:false}) 15 | }) 16 | } 17 | render() { 18 | return ( 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {!this.state.loading ? this.state.userList.map(user => { 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | }) :

Loading

} 51 | 52 |
Full nameBirthdayGenderEmailPhoneAddressBalance
admin10/09/2001male
{user.name}{user.birthday ? new Date(user.birthday).toLocaleDateString():'none'}{user.gender?"Male":"Female"}{user.email}{user.phone}{user.address}{user.balance}
53 |
54 | ); 55 | } 56 | } 57 | export default UserTable; 58 | -------------------------------------------------------------------------------- /client/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/actions/authActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import setAuthToken from "../utils/setAuthToken"; 3 | import jwt_decode from "jwt-decode"; 4 | 5 | import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from "./types"; 6 | 7 | // Register User 8 | export const registerUser = (userData, history) => dispatch => { 9 | axios 10 | .post("/api/users/register", userData) 11 | .then(res => history.push("/login")) 12 | .catch(err => 13 | dispatch({ 14 | type: GET_ERRORS, 15 | payload: err.response.data 16 | }) 17 | ); 18 | }; 19 | export const updateUser = (userData, history) => (dispatch) => { 20 | axios 21 | .post("/api/users/update", userData) 22 | .then((res) => { 23 | const { token } = res.data; 24 | localStorage.setItem("jwtToken", token); 25 | // Set token to Auth header 26 | setAuthToken(token); 27 | // Decode token to get user data 28 | const decoded = jwt_decode(token); 29 | // Set current user 30 | dispatch(setCurrentUser(decoded)); 31 | history.push("/info"); 32 | }) 33 | .catch((err) => 34 | dispatch({ 35 | type: GET_ERRORS, 36 | payload: err.response.data, 37 | }) 38 | ); 39 | }; 40 | // Login - get user token 41 | export const loginUser = userData => dispatch => { 42 | axios 43 | .post("/api/users/login", userData) 44 | .then(res => { 45 | // Save to localStorage 46 | 47 | // Set token to localStorage 48 | const { token } = res.data; 49 | localStorage.setItem("jwtToken", token); 50 | // Set token to Auth header 51 | setAuthToken(token); 52 | // Decode token to get user data 53 | const decoded = jwt_decode(token); 54 | // Set current user 55 | dispatch(setCurrentUser(decoded)); 56 | }) 57 | .catch(err => 58 | dispatch({ 59 | type: GET_ERRORS, 60 | payload: err.response.data 61 | }) 62 | ); 63 | }; 64 | 65 | 66 | // Set logged in user 67 | export const setCurrentUser = decoded => { 68 | return { 69 | type: SET_CURRENT_USER, 70 | payload: decoded 71 | }; 72 | }; 73 | 74 | // User loading 75 | export const setUserLoading = () => { 76 | return { 77 | type: USER_LOADING 78 | }; 79 | }; 80 | 81 | // Log user out 82 | export const logoutUser = () => dispatch => { 83 | // Remove token from local storage 84 | localStorage.removeItem("jwtToken"); 85 | // Remove auth header for future requests 86 | setAuthToken(false); 87 | // Set current user to empty object {} which will set isAuthenticated to false 88 | dispatch(setCurrentUser({})); 89 | }; 90 | -------------------------------------------------------------------------------- /client/public/js/script.js: -------------------------------------------------------------------------------- 1 | function getDate(string) { 2 | let re = /(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})/; 3 | let res = re.exec(string); 4 | if (!res) { 5 | console.log(res.input + "ERROR"); 6 | } else { 7 | let x = res[0].split(/\/|-|\./gi); 8 | console.log(x); 9 | return new Date( 10 | parseInt(x[2]), 11 | parseInt(x[1]) - 1, 12 | parseInt(x[0]) 13 | ).toISOString(); 14 | } 15 | } 16 | $(document).ready(function () { 17 | 18 | $("#stocks_crawl").click(function () { 19 | alert("ok"); 20 | fetch("/stocks/crawls", { 21 | method: "POST", 22 | }) 23 | .then(() => alert("done")) 24 | .catch((err) => console.log(err)); 25 | }); 26 | $("#message_create").click(function () { 27 | let data = { 28 | message: $("#message_name").val(), 29 | intent: $("#intent_name").val(), 30 | }; 31 | fetch("/wit/messages", { 32 | method: "POST", 33 | body: JSON.stringify(data), 34 | headers: { "Content-type": "application/json; charset=UTF-8" }, 35 | }) 36 | .then((response) => response.json()) 37 | .then((json) => console.log(json)) 38 | .catch((err) => console.log(err)); 39 | //alert("ok"); 40 | }); 41 | $("#keyword_sync").click(function () { 42 | let data = { 43 | entity: $("#entity_name").val(), 44 | }; 45 | fetch("/wit/keywords/sync", { 46 | method: "POST", 47 | body: JSON.stringify(data), 48 | headers: { "Content-type": "application/json; charset=UTF-8" }, 49 | }) 50 | .then((response) => response.json()) 51 | .then((json) => console.log(json)) 52 | .catch((err) => console.log(err)); 53 | //alert("ok"); 54 | }); 55 | $("#stock_create").click(function () { 56 | let data = { 57 | code: $("#code_name").val(), 58 | date: $("#date_name").val(), 59 | notification: $("#notification_name").val(), 60 | link: $("#link_name").val(), 61 | link_pdf: $("#link_pdf_name").val(), 62 | }; 63 | fetch("/stocks", { 64 | method: "POST", 65 | body: JSON.stringify(data), 66 | headers: { "Content-type": "application/json; charset=UTF-8" }, 67 | }) 68 | .then((response) => response.json()) 69 | .then((json) => console.log(json)) 70 | .catch((err) => console.log(err)); 71 | //alert("ok"); 72 | }); 73 | 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /client/src/components/Auth/login.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto:300); 2 | 3 | 4 | 5 | .form { 6 | position: relative; 7 | z-index: 1; 8 | background: #FFFFFF; 9 | max-width: 360px; 10 | margin: 0 auto 100px; 11 | padding: 45px; 12 | text-align: center; 13 | box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); 14 | } 15 | 16 | .form input { 17 | font-family: "Roboto", sans-serif; 18 | outline: 0; 19 | background: #f2f2f2; 20 | width: 100%; 21 | border: 0; 22 | margin: 0 0 15px; 23 | padding: 15px; 24 | box-sizing: border-box; 25 | font-size: 14px; 26 | } 27 | 28 | .form button { 29 | font-family: "Roboto", sans-serif; 30 | text-transform: uppercase; 31 | outline: 0; 32 | background: #4CAF50; 33 | width: 100%; 34 | border: 0; 35 | padding: 15px; 36 | color: #FFFFFF; 37 | font-size: 14px; 38 | -webkit-transition: all 0.3 ease; 39 | transition: all 0.3 ease; 40 | cursor: pointer; 41 | } 42 | 43 | .form button:hover, 44 | .form button:active, 45 | .form button:focus { 46 | background: #43A047; 47 | } 48 | 49 | .form .message { 50 | margin: 15px 0 0; 51 | color: #b3b3b3; 52 | font-size: 12px; 53 | } 54 | 55 | .form .message a { 56 | color: #4CAF50; 57 | text-decoration: none; 58 | } 59 | 60 | .form .register-form { 61 | display: none; 62 | } 63 | 64 | .container { 65 | position: relative; 66 | z-index: 1; 67 | max-width: 300px; 68 | margin: 0 auto; 69 | } 70 | 71 | .container:before, 72 | .container:after { 73 | content: ""; 74 | display: block; 75 | clear: both; 76 | } 77 | 78 | .container .info { 79 | margin: 50px auto; 80 | text-align: center; 81 | } 82 | 83 | .container .info h1 { 84 | margin: 0 0 15px; 85 | padding: 0; 86 | font-size: 36px; 87 | font-weight: 300; 88 | color: #1a1a1a; 89 | } 90 | 91 | .container .info span { 92 | color: #4d4d4d; 93 | font-size: 12px; 94 | } 95 | 96 | .container .info span a { 97 | color: #000000; 98 | text-decoration: none; 99 | } 100 | 101 | .container .info span .fa { 102 | color: #EF3B3A; 103 | } 104 | 105 | body { 106 | background: #76b852; 107 | /* fallback for old browsers */ 108 | background: -webkit-linear-gradient(right, #76b852, #8DC26F); 109 | background: -moz-linear-gradient(right, #76b852, #8DC26F); 110 | background: -o-linear-gradient(right, #76b852, #8DC26F); 111 | background: linear-gradient(to left, #76b852, #8DC26F); 112 | font-family: "Roboto", sans-serif; 113 | -webkit-font-smoothing: antialiased; 114 | -moz-osx-font-smoothing: grayscale; 115 | } -------------------------------------------------------------------------------- /client/src/components/Auth/login.js: -------------------------------------------------------------------------------- 1 | import "./login.css"; 2 | import axios from "axios"; 3 | import React, { Component } from "react"; 4 | import { Link } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | import { connect } from "react-redux"; 7 | import { loginUser } from "../../actions/authActions"; 8 | import classnames from "classnames"; 9 | 10 | class Login extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | email: "", 15 | password: "", 16 | errors: {}, 17 | }; 18 | } 19 | componentDidMount() { 20 | // If logged in and user navigates to Login page, should redirect them to dashboard 21 | if (this.props.auth.isAuthenticated) { 22 | this.props.history.push("/dashboard"); 23 | } 24 | } 25 | 26 | componentWillReceiveProps(nextProps) { 27 | if (nextProps.auth.isAuthenticated) { 28 | this.props.history.push("/dashboard"); 29 | } 30 | 31 | if (nextProps.errors) { 32 | this.setState({ 33 | errors: nextProps.errors, 34 | }); 35 | } 36 | } 37 | 38 | onChange = (e) => { 39 | this.setState({ [e.target.id]: e.target.value }); 40 | }; 41 | 42 | onSubmit = (e) => { 43 | e.preventDefault(); 44 | 45 | const userData = { 46 | email: this.state.email, 47 | password: this.state.password, 48 | }; 49 | 50 | this.props.loginUser(userData); 51 | }; 52 | render() { 53 | const { errors } = this.state; 54 | return ( 55 |
56 | 74 |
75 | ); 76 | } 77 | } 78 | Login.propTypes = { 79 | loginUser: PropTypes.func.isRequired, 80 | auth: PropTypes.object.isRequired, 81 | errors: PropTypes.object.isRequired, 82 | }; 83 | 84 | const mapStateToProps = (state) => ({ 85 | auth: state.auth, 86 | errors: state.errors, 87 | }); 88 | 89 | export default connect(mapStateToProps, { loginUser })(Login); 90 | -------------------------------------------------------------------------------- /client/public/css/owl.carousel.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Core Owl Carousel CSS File 3 | * v1.3.3 4 | */ 5 | 6 | /* clearfix */ 7 | .owl-carousel .owl-wrapper:after { 8 | content: "."; 9 | display: block; 10 | clear: both; 11 | visibility: hidden; 12 | line-height: 0; 13 | height: 0; 14 | } 15 | /* display none until init */ 16 | .owl-carousel{ 17 | display: none; 18 | position: relative; 19 | width: 100%; 20 | -ms-touch-action: pan-y; 21 | } 22 | .owl-carousel .owl-wrapper{ 23 | display: none; 24 | position: relative; 25 | -webkit-transform: translate3d(0px, 0px, 0px); 26 | } 27 | .owl-carousel .owl-wrapper-outer{ 28 | overflow: hidden; 29 | position: relative; 30 | width: 100%; 31 | } 32 | .owl-carousel .owl-wrapper-outer.autoHeight{ 33 | -webkit-transition: height 500ms ease-in-out; 34 | -moz-transition: height 500ms ease-in-out; 35 | -ms-transition: height 500ms ease-in-out; 36 | -o-transition: height 500ms ease-in-out; 37 | transition: height 500ms ease-in-out; 38 | } 39 | 40 | .owl-carousel .owl-item{ 41 | float: left; 42 | } 43 | .owl-controls .owl-page, 44 | .owl-controls .owl-buttons div{ 45 | cursor: pointer; 46 | } 47 | .owl-controls { 48 | -webkit-user-select: none; 49 | -khtml-user-select: none; 50 | -moz-user-select: none; 51 | -ms-user-select: none; 52 | user-select: none; 53 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 54 | } 55 | 56 | /* mouse grab icon */ 57 | .grabbing { 58 | cursor:url(grabbing.png) 8 8, move; 59 | } 60 | 61 | /* fix */ 62 | .owl-carousel .owl-wrapper, 63 | .owl-carousel .owl-item{ 64 | -webkit-backface-visibility: hidden; 65 | -moz-backface-visibility: hidden; 66 | -ms-backface-visibility: hidden; 67 | -webkit-transform: translate3d(0,0,0); 68 | -moz-transform: translate3d(0,0,0); 69 | -ms-transform: translate3d(0,0,0); 70 | } 71 | 72 | /* Feel free to change duration */ 73 | .animated { 74 | -webkit-animation-duration : 1000 ms ; 75 | animation-duration : 1000 ms ; 76 | -webkit-animation-fill-mode : both ; 77 | animation-fill-mode : both ; 78 | } 79 | /* .owl-animated-out - only for current item */ 80 | /* This is very important class. Use z-index if you want move Out item above In item */ 81 | .owl-animated-out { 82 | z-index : 1 83 | } 84 | /* .owl-animated-in - only for upcoming item 85 | /* This is very important class. Use z-index if you want move In item above Out item */ 86 | .owl-animated-in { 87 | z-index : 0 88 | } 89 | /* .fadeOut is style taken from Animation.css and this is how it looks in owl.carousel.css: */ 90 | .fadeOut { 91 | -webkit-animation-name : fadeOut ; 92 | animation-name : fadeOut ; 93 | } 94 | @-webkit-keyframes fadeOut { 95 | 0% { 96 | opacity : 1 ; 97 | } 98 | 100% { 99 | opacity : 0 ; 100 | } 101 | } 102 | @keyframes fadeOut { 103 | 0% { 104 | opacity : 1 ; 105 | } 106 | 100% { 107 | opacity : 0 ; 108 | } 109 | } -------------------------------------------------------------------------------- /client/src/components/Layout/header.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap"); 2 | 3 | :root { 4 | --delay: 0s; 5 | } 6 | 7 | @-webkit-keyframes top { 8 | 0% { 9 | transform: translateY(-30px); 10 | opacity: 0; 11 | } 12 | 13 | 100% { 14 | opacity: 1; 15 | transform: none; 16 | } 17 | } 18 | 19 | @keyframes top { 20 | 0% { 21 | transform: translateY(-30px); 22 | opacity: 0; 23 | } 24 | 25 | 100% { 26 | opacity: 1; 27 | transform: none; 28 | } 29 | } 30 | a{ 31 | color: white; 32 | } 33 | .header { 34 | background: #bcb5c9; 35 | background: radial-gradient(circle, #4B00D6 0%, #4B00D6 100%); 36 | width: 100%; 37 | height: 48px; 38 | padding: 0 30px; 39 | /* -webkit-animation: top 1s both; */ 40 | /* animation: top 1s both; */ 41 | display: flex; 42 | align-items: center; 43 | justify-content: center; 44 | border-radius: 6px; 45 | font-size: 15px; 46 | white-space: nowrap; 47 | position: -webkit-sticky; 48 | position: sticky; 49 | top: 0; 50 | left: 0; 51 | z-index: 10; 52 | } 53 | .header-link { 54 | color: #ffffff; 55 | text-decoration: none; 56 | display: flex; 57 | align-items: center; 58 | justify-content: center; 59 | font-size: large; 60 | text-align: center; 61 | padding: 10px; 62 | transition: 0.3s; 63 | border-bottom: 3px solid transparent; 64 | transition: 0.3s; 65 | } 66 | 67 | .header-link svg { 68 | width: 20px; 69 | margin-right: 14px; 70 | 71 | } 72 | 73 | .header-link.active, 74 | .header-link:hover { 75 | color: #070155; 76 | background: #c7a48a; 77 | border-bottom: 3px solid #d44297; 78 | } 79 | 80 | .logo { 81 | padding: 20px 48px 20px 0; 82 | font-size: 16px; 83 | color: #e7e8ea; 84 | } 85 | 86 | .logo-det { 87 | background: #4255d4; 88 | padding: 8px; 89 | margin-left: -2px; 90 | border-radius: 50%; 91 | font-size: 15px; 92 | } 93 | 94 | .user-info { 95 | margin-left: auto; 96 | display: flex; 97 | align-items: center; 98 | } 99 | 100 | .user-info svg { 101 | width: 20px; 102 | } 103 | 104 | .user-info .profile { 105 | margin: 0 20px 0 12px; 106 | width: 18px; 107 | } 108 | 109 | .button { 110 | display: flex; 111 | align-items: center; 112 | color: #9b9ca7; 113 | background: #1a1b3c; 114 | border: none; 115 | padding: 2px 12px; 116 | border-radius: 4px; 117 | margin-right: 20px; 118 | } 119 | 120 | .button svg { 121 | margin-left: 10px; 122 | width: 16px; 123 | } 124 | 125 | .hour{ 126 | color:#EDC224; 127 | } 128 | @media screen and (max-width: 1060px) { 129 | 130 | .user-info .button, 131 | .user-info .hour { 132 | display: none; 133 | } 134 | } 135 | 136 | @media screen and (max-width: 930px) { 137 | 138 | .header-link { 139 | display: none; 140 | } 141 | 142 | .user-info .profile { 143 | margin-right: 0; 144 | } 145 | } -------------------------------------------------------------------------------- /client/public/assets/img/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 1608302934293 50 | 55 | 56 | 57 | 58 | 60 | 61 | 70 | 71 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/public/css/main.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "../scss/main.css", 4 | "sources": [ 5 | "../scss/main.scss", 6 | "../scss/_hero-area.scss" 7 | ], 8 | "sourcesContent": [ 9 | "\r\n\r\n// // Global Styles\r\n// @import \"global\";\r\n\r\n// // Navbar\r\n// @import \"navbar\";\r\n\r\n// Hero Slider \r\n@import \"hero-area\";\r\n\r\n// // About US Area\r\n// @import \"about\";\r\n\r\n// // Service Area\r\n// @import \"service\";\r\n\r\n// // Featured \r\n// @import \"featured\";\r\n\r\n// // Team Area\r\n// @import \"team\";\r\n\r\n// // Testimonial Area\r\n// @import \"testimonial\";\r\n\r\n// // Pricing\r\n// @import 'pricing';\r\n\r\n// // Portfolio\r\n// @import 'portfolio';\r\n\r\n// // Carousel\r\n// @import 'carousel';\r\n\r\n// // Blog Area\r\n// @import \"blog\";\r\n\r\n// // Contact Area\r\n// @import \"contact\";\r\n\r\n// // Contact Area\r\n// @import \"footer\";\r\n\r\n// Counter Page\r\n// @import \"counter\";\r\n\r\n", 10 | "/* ==========================================================================\r\n Hero Area\r\n ========================================================================== */\r\n.hero-area {\r\n background: url('../img/intro.png') no-repeat center 65%;\r\n color: #fff;\r\n overflow: hidden;\r\n position: relative;\r\n background-size: 50%;\r\n .overlay{\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n top: 0px;\r\n left: 0px;\r\n background: rgb(85,51,255);\r\n background: linear-gradient(95deg, rgba(85,51,255,1) 40%, rgba(37,221,245,1) 100%);\r\n -webkit-transform: skewY(-12deg);\r\n transform: skewY(-12deg);\r\n -webkit-transform-origin: 0;\r\n transform-origin: 0;\r\n z-index: -12\r\n }\r\n .contents{\r\n padding: 250px 0 280px;\r\n h2{ \r\n color: #fff;\r\n font-size: 48px;\r\n font-weight: 600;\r\n line-height: 60px;\r\n margin-bottom: 20px;\r\n }\r\n p{\r\n color: #fff;\r\n font-size: 18px;\r\n line-height: 26px;\r\n }\r\n .btn{\r\n margin-top: 40px;\r\n margin-right: 20px;\r\n text-transform: uppercase;\r\n padding: 14px 40px;\r\n }\r\n .btn-border{\r\n border: 1px solid #fff;\r\n color: #fff;\r\n }\r\n }\r\n\r\n .intro-img{\r\n padding: 148px 0 0px;\r\n img{\r\n display: block;\r\n height: auto;\r\n max-width: 100%;\r\n }\r\n }\r\n }\r\n" 11 | ], 12 | "mappings": "ACAA;;gFAEgF;;AAChF,AAAA,UAAU,CAAC;EACT,UAAU,EAAE,uBAAG,CAAqB,SAAS,CAAC,MAAM,CAAC,GAAG;EACxD,KAAK,EAAE,IAAK;EACZ,QAAQ,EAAE,MAAO;EACjB,QAAQ,EAAE,QAAS;EACnB,eAAe,EAAE,GAAI;CAiDpB;;;AAtDH,AAMI,UANM,CAMN,QAAQ,CAAA;EACN,QAAQ,EAAE,QAAS;EACnB,KAAK,EAAE,IAAK;EACZ,MAAM,EAAE,IAAK;EACb,GAAG,EAAE,GAAI;EACT,IAAI,EAAE,GAAI;EACZ,UAAU,EAAE,OAAG;EACf,UAAU,EAAE,iDAAe;EACvB,iBAAiB,EAAE,aAAK;EACxB,SAAS,EAAE,aAAK;EAChB,wBAAwB,EAAE,CAAE;EAC5B,gBAAgB,EAAE,CAAE;EACpB,OAAO,EAAE,GACZ;CAAC;;;AAnBN,AAoBI,UApBM,CAoBN,SAAS,CAAA;EACL,OAAO,EAAE,aAAc;CAuB1B;;;AA5CL,AAsBM,UAtBI,CAoBN,SAAS,CAEP,EAAE,CAAA;EACN,KAAK,EAAE,IAAK;EACZ,SAAS,EAAE,IAAK;EAChB,WAAW,EAAE,GAAI;EACjB,WAAW,EAAE,IAAK;EAClB,aAAa,EAAE,IAAK;CACf;;;AA5BP,AA6BM,UA7BI,CAoBN,SAAS,CASP,CAAC,CAAA;EACL,KAAK,EAAE,IAAK;EACZ,SAAS,EAAE,IAAK;EAChB,WAAW,EAAE,IAAK;CACb;;;AAjCP,AAkCM,UAlCI,CAoBN,SAAS,CAcP,IAAI,CAAA;EACR,UAAU,EAAE,IAAK;EACjB,YAAY,EAAE,IAAK;EACnB,cAAc,EAAE,SAAU;EAC1B,OAAO,EAAE,SAAU;CACd;;;AAvCP,AAwCM,UAxCI,CAoBN,SAAS,CAoBP,WAAW,CAAA;EACT,MAAM,EAAE,cAAe;EACvB,KAAK,EAAE,IAAK;CACb;;;AA3CP,AA8CG,UA9CO,CA8CP,UAAU,CAAA;EACP,OAAO,EAAE,WAAY;CAMtB;;;AArDL,AAgDM,UAhDI,CA8CP,UAAU,CAEP,GAAG,CAAA;EACD,OAAO,EAAE,KAAM;EACf,MAAM,EAAE,IAAK;EACb,SAAS,EAAE,IAAK;CACjB", 13 | "names": [] 14 | } -------------------------------------------------------------------------------- /client/src/components/Auth/register.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./login.css"; 3 | import axios from "axios"; 4 | import { Link, withRouter } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | import { connect } from "react-redux"; 7 | import { registerUser } from "../../actions/authActions"; 8 | import classnames from "classnames"; 9 | class Register extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | name: "", 14 | email: "", 15 | password: "", 16 | password2: "", 17 | errors: {}, 18 | }; 19 | } 20 | componentDidMount() { 21 | // If logged in and user navigates to Register page, should redirect them to dashboard 22 | if (this.props.auth.isAuthenticated) { 23 | this.props.history.push("/dashboard"); 24 | } 25 | } 26 | 27 | componentWillReceiveProps(nextProps) { 28 | if (nextProps.errors) { 29 | this.setState({ 30 | errors: nextProps.errors, 31 | }); 32 | } 33 | } 34 | 35 | onChange = (e) => { 36 | this.setState({ [e.target.id]: e.target.value }); 37 | }; 38 | 39 | onSubmit = (e) => { 40 | e.preventDefault(); 41 | 42 | const newUser = { 43 | name: this.state.name, 44 | email: this.state.email, 45 | password: this.state.password, 46 | password2: this.state.password2, 47 | }; 48 | 49 | this.props.registerUser(newUser, this.props.history); 50 | }; 51 | render() { 52 | const { errors } = this.state; 53 | return ( 54 |
55 | 78 |
79 | ); 80 | } 81 | } 82 | Register.propTypes = { 83 | registerUser: PropTypes.func.isRequired, 84 | auth: PropTypes.object.isRequired, 85 | errors: PropTypes.object.isRequired 86 | }; 87 | 88 | const mapStateToProps = state => ({ 89 | auth: state.auth, 90 | errors: state.errors 91 | }); 92 | 93 | export default connect(mapStateToProps,{ registerUser })(withRouter(Register)); 94 | -------------------------------------------------------------------------------- /client/src/lib/bootstrap/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /client/public/js/main.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | "use strict"; 4 | 5 | /* Page Loader active 6 | ========================================================*/ 7 | $('#preloader').fadeOut(); 8 | 9 | /* Testimonials Carousel 10 | ========================================================*/ 11 | var owl = $("#client-testimonial"); 12 | owl.owlCarousel({ 13 | navigation: true, 14 | pagination: false, 15 | slideSpeed: 1000, 16 | stopOnHover: true, 17 | autoPlay: true, 18 | items: 1, 19 | animateIn: 'fadeIn', 20 | animateOut: 'fadeOut', 21 | addClassActive: true, 22 | itemsDesktop : [1199,1], 23 | itemsDesktopSmall : [980,1], 24 | itemsTablet: [768,1], 25 | itemsTablet: [767,1], 26 | itemsTabletSmall: [480,1], 27 | itemsMobile : [479,1], 28 | }); 29 | $('#client-testimonial').find('.owl-prev').html(''); 30 | $('#client-testimonial').find('.owl-next').html(''); 31 | 32 | 33 | /* showcase Slider 34 | =============================*/ 35 | var owl = $(".showcase-slider"); 36 | owl.owlCarousel({ 37 | navigation: false, 38 | pagination: true, 39 | slideSpeed: 1000, 40 | margin:10, 41 | stopOnHover: true, 42 | autoPlay: true, 43 | items: 5, 44 | itemsDesktopSmall: [1024, 3], 45 | itemsTablet: [600, 1], 46 | itemsMobile: [479, 1] 47 | }); 48 | 49 | 50 | 51 | /* 52 | Sticky Nav 53 | ========================================================================== */ 54 | $(window).on('scroll', function() { 55 | if ($(window).scrollTop() > 100) { 56 | $('.header-top-area').addClass('menu-bg'); 57 | } else { 58 | $('.header-top-area').removeClass('menu-bg'); 59 | } 60 | }); 61 | 62 | /* 63 | VIDEO POP-UP 64 | ========================================================================== */ 65 | $('.video-popup').magnificPopup({ 66 | disableOn: 700, 67 | type: 'iframe', 68 | mainClass: 'mfp-fade', 69 | removalDelay: 160, 70 | preloader: false, 71 | fixedContentPos: false, 72 | }); 73 | 74 | /* 75 | Back Top Link 76 | ========================================================================== */ 77 | var offset = 200; 78 | var duration = 500; 79 | $(window).scroll(function() { 80 | if ($(this).scrollTop() > offset) { 81 | $('.back-to-top').fadeIn(400); 82 | } else { 83 | $('.back-to-top').fadeOut(400); 84 | } 85 | }); 86 | 87 | $('.back-to-top').on('click',function(event) { 88 | event.preventDefault(); 89 | $('html, body').animate({ 90 | scrollTop: 0 91 | }, 600); 92 | return false; 93 | }) 94 | 95 | /* 96 | One Page Navigation 97 | ========================================================================== */ 98 | 99 | 100 | $(window).on('load', function() { 101 | 102 | $('body').scrollspy({ 103 | target: '.navbar-collapse', 104 | offset: 195 105 | }); 106 | 107 | $(window).on('scroll', function() { 108 | if ($(window).scrollTop() > 100) { 109 | $('.fixed-top').addClass('menu-bg'); 110 | } else { 111 | $('.fixed-top').removeClass('menu-bg'); 112 | } 113 | }); 114 | 115 | }); 116 | 117 | /* Auto Close Responsive Navbar on Click 118 | ========================================================*/ 119 | function close_toggle() { 120 | if ($(window).width() <= 768) { 121 | $('.navbar-collapse a').on('click', function () { 122 | $('.navbar-collapse').collapse('hide'); 123 | }); 124 | } 125 | else { 126 | $('.navbar .navbar-inverse a').off('click'); 127 | } 128 | } 129 | close_toggle(); 130 | $(window).resize(close_toggle); 131 | 132 | /* Nivo Lightbox 133 | ========================================================*/ 134 | $('.lightbox').nivoLightbox({ 135 | effect: 'fadeScale', 136 | keyboardNav: true, 137 | }); 138 | 139 | }(jQuery)); 140 | 141 | -------------------------------------------------------------------------------- /client/src/components/Layout/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "./header.css"; 7 | // import "./layout.css"; 8 | import { logoutUser } from "../../actions/authActions"; 9 | class HeaderLeft extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | const { user } = this.props.auth; 13 | this.state = { 14 | name: user.name, 15 | time:new Date().toLocaleTimeString() 16 | } 17 | } 18 | componentDidMount(){ 19 | setInterval(() => 20 | this.setState({time:new Date().toLocaleTimeString()}) 21 | ,1000) 22 | } 23 | render() { 24 | return ( 25 |
26 |
27 | NPV2k1 28 | Cr 29 |
30 | 31 | Home 32 | 33 | 34 | Products 35 | 36 | 37 | Map 38 | 39 | 40 | Reports 41 | 42 |
43 | 44 | Login 45 | 46 | 47 | Logout 48 | 49 | 66 |
{this.state.name}
67 | 68 | 69 | 70 |
{this.state.time}
71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | 78 | HeaderLeft.propTypes = { 79 | logoutUser: PropTypes.func.isRequired, 80 | auth: PropTypes.object.isRequired, 81 | }; 82 | 83 | const mapStateToProps = (state) => ({ auth: state.auth }); 84 | 85 | const HeaderL= connect(mapStateToProps, { logoutUser })(HeaderLeft); 86 | 87 | class NewHeader extends React.Component { 88 | constructor(props) { 89 | super(props); 90 | this.state = { item: [] }; 91 | } 92 | render() { 93 | return ( 94 |
95 | 96 |
97 | ); 98 | } 99 | } 100 | 101 | export default NewHeader; 102 | -------------------------------------------------------------------------------- /server/routes/api/users.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const bcrypt = require("bcryptjs"); 4 | const jwt = require("jsonwebtoken"); 5 | 6 | const passport = require("passport"); 7 | 8 | require("dotenv").config(); 9 | // Load input validation 10 | const validateRegisterInput = require("../../validation/register"); 11 | const validateLoginInput = require("../../validation/login"); 12 | 13 | // Load User model 14 | const User = require("../../model/User"); 15 | 16 | // @route POST api/users/register 17 | // @desc Register user 18 | // @access Public 19 | router.post("/register", (req, res) => { 20 | // Form validation 21 | 22 | const { errors, isValid } = validateRegisterInput(req.body); 23 | 24 | // Check validation 25 | if (!isValid) { 26 | return res.status(400).json(errors); 27 | } 28 | 29 | User.findOne({ email: req.body.email }).then((user) => { 30 | if (user) { 31 | return res.status(400).json({ email: "Email already exists" }); 32 | } else { 33 | const newUser = new User({ 34 | name: req.body.name, 35 | email: req.body.email, 36 | password: req.body.password, 37 | }); 38 | 39 | // Hash password before saving in database 40 | bcrypt.genSalt(10, (err, salt) => { 41 | bcrypt.hash(newUser.password, salt, (err, hash) => { 42 | if (err) throw err; 43 | newUser.password = hash; 44 | newUser 45 | .save() 46 | .then((user) => res.json(user)) 47 | .catch((err) => console.log(err)); 48 | }); 49 | }); 50 | } 51 | }); 52 | }); 53 | 54 | // @route POST api/users/login 55 | // @desc Login user and return JWT token 56 | // @access Public 57 | router.post("/login", (req, res) => { 58 | // Form validation 59 | 60 | const { errors, isValid } = validateLoginInput(req.body); 61 | 62 | // Check validation 63 | if (!isValid) { 64 | return res.status(400).json(errors); 65 | } 66 | 67 | const email = req.body.email; 68 | const password = req.body.password; 69 | 70 | // Find user by email 71 | User.findOne({ email }).then((user) => { 72 | // Check if user exists 73 | if (!user) { 74 | return res.status(404).json({ emailnotfound: "Email not found" }); 75 | } 76 | 77 | // Check password 78 | bcrypt.compare(password, user.password).then((isMatch) => { 79 | if (isMatch) { 80 | // User matched 81 | // Create JWT Payload 82 | 83 | const payload = { 84 | id: user.id, 85 | name: user.name, 86 | gender: user.gender, 87 | birthday: user.birthday, 88 | email: user.email, 89 | phone: user.phone, 90 | address: user.address, 91 | }; 92 | 93 | // Sign token 94 | jwt.sign( 95 | payload, 96 | process.env.JWT_SECRET, 97 | { 98 | expiresIn: 31556926, // 1 year in seconds 99 | }, 100 | (err, token) => { 101 | res.json({ 102 | success: true, 103 | token: "Bearer " + token, 104 | }); 105 | } 106 | ); 107 | } else { 108 | return res 109 | .status(400) 110 | .json({ passwordincorrect: "Password incorrect" }); 111 | } 112 | }); 113 | }); 114 | }); 115 | 116 | router.post("/update", (req, res) => { 117 | // const { errors, isValid } = validateRegisterInput(req.body); 118 | // if (!isValid) { 119 | // return res.status(400).json(errors); 120 | // } 121 | User.updateOne({ email: req.body.email }, { ...req.body }).then((msg) => { 122 | User.findOne({ email: req.body.email }).then((user) => { 123 | // Check if user exists 124 | if (!user) { 125 | return res.status(404).json({ emailnotfound: "Email not found" }); 126 | } 127 | const payload = { 128 | id: user.id, 129 | name: user.name, 130 | gender: user.gender, 131 | birthday: user.birthday, 132 | email: user.email, 133 | phone: user.phone, 134 | address: user.address, 135 | }; 136 | 137 | // Sign token 138 | jwt.sign( 139 | payload, 140 | process.env.JWT_SECRET, 141 | { 142 | expiresIn: 31556926, // 1 year in seconds 143 | }, 144 | (err, token) => { 145 | res.json({ 146 | success: true, 147 | token: "Bearer " + token, 148 | }); 149 | } 150 | ); 151 | }); 152 | }); 153 | }); 154 | 155 | module.exports = router; 156 | -------------------------------------------------------------------------------- /client/public/js/jquery.easing.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ 3 | * 4 | * Uses the built in easing capabilities added In jQuery 1.1 5 | * to offer multiple easing options 6 | * 7 | * TERMS OF USE - EASING EQUATIONS 8 | * 9 | * Open source under the BSD License. 10 | * 11 | * Copyright © 2001 Robert Penner 12 | * All rights reserved. 13 | * 14 | * TERMS OF USE - jQuery Easing 15 | * 16 | * Open source under the BSD License. 17 | * 18 | * Copyright © 2008 George McGinley Smith 19 | * All rights reserved. 20 | * 21 | * Redistribution and use in source and binary forms, with or without modification, 22 | * are permitted provided that the following conditions are met: 23 | * 24 | * Redistributions of source code must retain the above copyright notice, this list of 25 | * conditions and the following disclaimer. 26 | * Redistributions in binary form must reproduce the above copyright notice, this list 27 | * of conditions and the following disclaimer in the documentation and/or other materials 28 | * provided with the distribution. 29 | * 30 | * Neither the name of the author nor the names of contributors may be used to endorse 31 | * or promote products derived from this software without specific prior written permission. 32 | * 33 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 34 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 35 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 36 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 37 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 39 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 40 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 41 | * OF THE POSSIBILITY OF SUCH DAMAGE. 42 | * 43 | */ 44 | jQuery.easing.jswing=jQuery.easing.swing;jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,f,a,h,g){return jQuery.easing[jQuery.easing.def](e,f,a,h,g)},easeInQuad:function(e,f,a,h,g){return h*(f/=g)*f+a},easeOutQuad:function(e,f,a,h,g){return -h*(f/=g)*(f-2)+a},easeInOutQuad:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f+a}return -h/2*((--f)*(f-2)-1)+a},easeInCubic:function(e,f,a,h,g){return h*(f/=g)*f*f+a},easeOutCubic:function(e,f,a,h,g){return h*((f=f/g-1)*f*f+1)+a},easeInOutCubic:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f+a}return h/2*((f-=2)*f*f+2)+a},easeInQuart:function(e,f,a,h,g){return h*(f/=g)*f*f*f+a},easeOutQuart:function(e,f,a,h,g){return -h*((f=f/g-1)*f*f*f-1)+a},easeInOutQuart:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+a}return -h/2*((f-=2)*f*f*f-2)+a},easeInQuint:function(e,f,a,h,g){return h*(f/=g)*f*f*f*f+a},easeOutQuint:function(e,f,a,h,g){return h*((f=f/g-1)*f*f*f*f+1)+a},easeInOutQuint:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+a}return h/2*((f-=2)*f*f*f*f+2)+a},easeInSine:function(e,f,a,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+a},easeOutSine:function(e,f,a,h,g){return h*Math.sin(f/g*(Math.PI/2))+a},easeInOutSine:function(e,f,a,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+a},easeInExpo:function(e,f,a,h,g){return(f==0)?a:h*Math.pow(2,10*(f/g-1))+a},easeOutExpo:function(e,f,a,h,g){return(f==g)?a+h:h*(-Math.pow(2,-10*f/g)+1)+a},easeInOutExpo:function(e,f,a,h,g){if(f==0){return a}if(f==g){return a+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+a}return h/2*(-Math.pow(2,-10*--f)+2)+a},easeInCirc:function(e,f,a,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+a},easeOutCirc:function(e,f,a,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+a},easeInOutCirc:function(e,f,a,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+a}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+a},easeInElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g 56 |
57 |
58 |
59 |

60 | Thông tin cá nhân 61 |

62 |
63 |
64 |
65 |
66 |
67 |
68 | 71 | this.changeName(e)} 77 | value={this.state.name} 78 | /> 79 |
80 |
81 |
82 |
83 |
84 |
85 | 88 |
89 | 96 | e.target.checked 97 | ? this.setState({ gender: 1 }) 98 | : this.setState({ gender: 0 }) 99 | } 100 | checked={this.state.gender ? "checked" : ""} 101 | /> 102 | 105 |
106 |
107 | 114 | e.target.checked 115 | ? this.setState({ gender: 0 }) 116 | : this.setState({ gender: 1 }) 117 | } 118 | checked={this.state.gender ? "" : "checked"} 119 | /> 120 | 123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 133 | this.changeBirthday(e)} 139 | value={this.state.birthday ? this.state.birthday : ""} 140 | /> 141 |
142 |
143 |
144 |
145 |
146 |
147 | 150 | 158 |
159 |
160 |
161 |
162 |
163 |
164 | 167 | this.changePhone(e)} 172 | value={this.state.phone} 173 | name="first_name" 174 | /> 175 |
176 |
177 |
178 |
179 |
180 |
181 | 185 | this.changeAddress(e)} 190 | value={this.state.address ? this.state.address : ""} 191 | name="address" 192 | /> 193 |
194 |
195 |
196 |
197 |
198 |
199 | 203 | 205 | this.setState({ password: e.target.value }) 206 | } 207 | type="password" 208 | className="form-control" 209 | value={this.state.password} 210 | /> 211 |
212 |
213 |
214 |
215 | 219 | 221 | this.setState({ password2: e.target.value }) 222 | } 223 | type="password" 224 | value={this.state.password2} 225 | className="form-control" 226 | /> 227 |
228 |
229 |
230 |
231 | 234 |
235 |
236 |
237 |
238 |
239 | 240 | ); 241 | } 242 | } 243 | InfoUser.propTypes = { 244 | logoutUser: PropTypes.func.isRequired, 245 | auth: PropTypes.object.isRequired, 246 | }; 247 | 248 | const mapStateToProps = (state) => ({ 249 | auth: state.auth, 250 | }); 251 | 252 | export default connect(mapStateToProps,{updateUser})(InfoUser); 253 | 254 | // export default InfoUser 255 | -------------------------------------------------------------------------------- /client/public/css/responsive.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 2160px) { 2 | #showcase { 3 | padding-bottom: 530px; } 4 | #showcase .showcase-area { 5 | width: 98%; } 6 | #showcase .showcase-slider .screenshot-thumb p { 7 | color: #fff; 8 | font-size: 20px; 9 | font-style: italic; 10 | margin-bottom: 10px; } 11 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 12 | padding-top: 165px !important; } 13 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 14 | width: 89% !important; 15 | height: 89% !important; } 16 | .hero-area .overlay :nth-child(1) { 17 | height: 294px !important; } } 18 | 19 | @media screen and (min-width: 1920px) { 20 | .hero-area .contents { 21 | padding: 280px 0 390px; } 22 | .hero-area .overlay :nth-child(1) { 23 | height: 250px; } 24 | .showcase-slider .owl-item img { 25 | width: 100%; } 26 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 27 | width: 85%; } 28 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 29 | padding-top: 100px; } } 30 | 31 | @media (min-width: 768px) and (max-width: 1024px) { 32 | .hero-area .contents h2 { 33 | font-size: 38px; } 34 | .navbar-expand-md .navbar-nav .nav-link { 35 | padding: 12px 5px; 36 | font-size: 13px; 37 | margin-left: 15px; } 38 | .hero-area .contents { 39 | padding: 210px 0 280px; } 40 | #business-plan .business-item-info h3 { 41 | font-size: 28px; } 42 | #business-plan .business-item-info p { 43 | font-size: 13px; 44 | margin-bottom: 20px; } 45 | .section { 46 | padding: 80px 0px; } 47 | #business-plan { 48 | padding-bottom: 80px; } 49 | .feature-info { 50 | width: 60%; } 51 | #features .feature-item { 52 | padding: 15px; } 53 | #features .feature-item h4 { 54 | margin-bottom: 10px; } 55 | #showcase .gradient-bg { 56 | padding: 80px 0px 60px 0px; } 57 | #pricing { 58 | padding-bottom: 80px; } 59 | #testimonial #client-testimonial .content-inner p { 60 | font-size: 18px; 61 | color: #5e629c; 62 | font-style: italic; 63 | line-height: 35px; } 64 | #team .single-team .team-social-icons { 65 | bottom: 27%; } 66 | #blog .blog-item-wrapper h3 { 67 | font-size: 16px; 68 | line-height: 23px; } 69 | #footer-Content h3.block-title { 70 | font-size: 16px; } 71 | #footer-Content ul.menu li a { 72 | font-size: 14px; 73 | line-height: 31px; } 74 | #footer-Content { 75 | padding-top: 80px; } 76 | #footer-Content .copyright { 77 | margin-top: 80px; } 78 | .hero-area .contents h2 { 79 | color: #fff; 80 | font-size: 40px; 81 | font-weight: 600; 82 | line-height: 60px; 83 | margin-bottom: 20px; } 84 | #testimonial .testimonial-area { 85 | width: 98%; } 86 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 87 | height: 80%; 88 | width: 80%; } 89 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 90 | padding-top: 45px; } 91 | #footer-Content h3.block-title { 92 | margin-bottom: 20px; 93 | margin-top: 20px; } } 94 | 95 | @media (min-width: 768px) and (max-width: 991px) { 96 | .hero-area .contents h2 { 97 | color: #fff; 98 | font-size: 40px !important; } 99 | .navbar-expand-md .navbar-nav .nav-link { 100 | padding: 12px 0px; 101 | font-size: 13px; 102 | margin-left: 12px; } 103 | .hero-area .contents { 104 | padding: 200px 0 0px; } 105 | #features .feature-item p { 106 | font-size: 14px; 107 | display: block; 108 | line-height: 25px; } 109 | #pricing .pricing-table { 110 | padding: 30px 15px; } 111 | #pricing .pricing-table h1 span { 112 | left: 10%; } 113 | #team .single-team, .blog-item-wrapper, .services-item { 114 | margin-bottom: 20px; } 115 | .contact-img img { 116 | max-width: 100%; 117 | display: block; 118 | margin: 0px auto; } 119 | #footer-Content .footer-logo img { 120 | padding-top: 0; } 121 | #footer-Content .copyright p { 122 | color: #3ecf8e; 123 | font-size: 14px; } 124 | .hero-area .intro-img { 125 | padding: 100px 0 0px; } 126 | .blog-hero-area .contents h2 { 127 | font-size: 36px; } 128 | .btn-singin { 129 | background: #7fc9fb; 130 | color: #fff; 131 | padding: 10px 14px; 132 | margin-left: 12px; 133 | box-shadow: 0px 8px 9px 0px rgba(96, 94, 94, 0.17); 134 | font-size: 11px; } 135 | .btn-light-singin { 136 | background: #7fc9fb; 137 | color: #fff; 138 | padding: 10px 14px; 139 | margin-left: 12px; 140 | box-shadow: 0px 8px 9px 0px rgba(96, 94, 94, 0.17); 141 | font-size: 11px; } 142 | .navbar-expand-md .navbar-nav .nav-link { 143 | padding: 12px 0px; 144 | font-size: 13px; 145 | margin-left: 9px; } 146 | .pt-70 { 147 | padding-top: 20px; } 148 | .showcase-area .pr-0, .px-0 { 149 | padding-right: 15px !important; } 150 | #showcase { 151 | padding-bottom: 275px; } 152 | #testimonial .testimonial-area { 153 | width: 98%; } } 154 | 155 | @media only screen and (max-width: 767px) { 156 | .bg-inverse { 157 | background: #191c1e; } 158 | .navbar-expand-md .navbar-brand, .navbar-expand-md .navbar-toggler { 159 | margin: 6px 15px; } 160 | .bg-inverse { 161 | background: #191c1e; } 162 | .navbar { 163 | padding: 0; } 164 | .navbar-brand img { 165 | width: 100px; } } 166 | 167 | @media (min-width: 320px) and (max-width: 480px) { 168 | .bg-inverse { 169 | background: #191c1e; } 170 | .navbar { 171 | padding: 0; } 172 | .navbar-brand img { 173 | width: 100px; } 174 | .navbar-expand-md .navbar-brand, .navbar-expand-md .navbar-toggler { 175 | margin: 6px 15px; } 176 | .btn-singin { 177 | background: transparent; 178 | color: #fff; 179 | padding: 10px 23px; 180 | margin-left: 0; 181 | box-shadow: 0px 0px 0px 0px rgba(96, 94, 94, 0.17); } 182 | .hero-area .contents h2 { 183 | font-size: 20px; 184 | line-height: 35px; 185 | margin-bottom: 10px; } 186 | .hero-area .contents { 187 | padding: 200px 0 50px; } 188 | .hero-area .contents p { 189 | color: #fff; 190 | font-size: 14px; 191 | line-height: 26px; } 192 | .hero-area .contents .btn { 193 | margin-top: 30px; 194 | margin-right: 4px; 195 | text-transform: uppercase; 196 | width: 125px; 197 | height: 45px; 198 | padding: 15px 14px; 199 | font-size: 12px; } 200 | .hero-area .contents .btn-border { 201 | border: 1px solid #fff; 202 | color: #fff; 203 | -webkit-box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); 204 | box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); 205 | width: 122px; 206 | height: 45px; 207 | padding: 15px 15px; } 208 | .hero-area .intro-img { 209 | padding: 30px 0 0px; } 210 | .section { 211 | padding: 70px 0px; } 212 | #services .services-item { 213 | padding: 30px 15px; 214 | margin-bottom: 15px; } 215 | #business-plane .business-item-info h3 { 216 | font-size: 25px; } 217 | .section-header .section-title { 218 | font-size: 25px; } 219 | #services .services-item p { 220 | font-size: 14px; } 221 | #business-plane .business-item-info p { 222 | font-size: 14px; 223 | margin-bottom: 20px; } 224 | .section-header .desc-text p { 225 | font-size: 14px; 226 | color: #5e629c; 227 | line-height: 24px; 228 | margin-bottom: 0; } 229 | #features .feature-item { 230 | padding: 10px; } 231 | #features .feature-item .feature-icon { 232 | margin-bottom: 20px; } 233 | #features .feature-item p { 234 | font-size: 15px; 235 | display: block; } 236 | .showcase-area .pr-0, .px-0 { 237 | padding-right: 15px !important; } 238 | #showcase { 239 | padding-bottom: 250px; } 240 | #pricing .pricing-table h1 span { 241 | left: 4%; } 242 | #pricing .pricing-table { 243 | margin-bottom: 15px; } 244 | #testimonial #client-testimonial { 245 | padding: 30px 10px; } 246 | #testimonial #client-testimonial .content-inner p { 247 | font-size: 15px; 248 | line-height: 22px; } 249 | #testimonial #client-testimonial .author-info h5 { 250 | color: #151948; 251 | font-size: 15px; } 252 | #testimonial #client-testimonial h5 span { 253 | font-size: 13px; 254 | color: #5e629c; 255 | font-style: italic; } 256 | #testimonial .testimonial-area { 257 | position: inherit; 258 | width: 100%; 259 | z-index: 12; 260 | top: 0; 261 | margin: 0px auto; } 262 | #testimonial { 263 | margin-bottom: 0; 264 | background-size: cover; } 265 | #team .single-team { 266 | margin-bottom: 15px; } 267 | #team .single-team .team-social-icons { 268 | bottom: 22%; } 269 | #blog .blog-item-wrapper .blog-item-text { 270 | padding: 20px 15px 15px 15px; } 271 | #blog .blog-item-wrapper h3 { 272 | font-size: 18px; 273 | font-weight: 600; 274 | line-height: 25px; 275 | margin-bottom: 12px; } 276 | #blog .blog-item-wrapper { 277 | margin-bottom: 15px; } 278 | #footer-Content .copyright p { 279 | color: #3ecf8e; 280 | font-size: 12px; } 281 | #footer-Content ul.menu li a { 282 | color: #e2e2e2; 283 | font-size: 14px; 284 | font-weight: 400; 285 | line-height: 30px; } 286 | #footer-Content h3.block-title { 287 | color: #fff; 288 | font-size: 18px; 289 | font-weight: 600; 290 | margin-bottom: 15px; 291 | margin-top: 15px; } 292 | #footer-Content .footer-logo img { 293 | padding-top: 0; } 294 | #footer-Content { 295 | padding-top: 30px; 296 | background: #180e29; } 297 | #footer-Content .footer-logo img { 298 | padding-top: 0; 299 | width: 40%; } 300 | #blog .blog-item-wrapper .author { 301 | border-top: 1px solid #ddd; 302 | padding: 18px 10px 15px 10px; } 303 | .owl-theme .owl-controls { 304 | display: none !important; } 305 | #business-plane { 306 | padding-bottom: 40px; } 307 | #showcase .showcase-area { 308 | width: 90%; } 309 | #light-featured .light-feature-item .light-feature-icon { 310 | margin-bottom: 20px; } 311 | #light-featured .right-feature-info h3 { 312 | font-size: 25px; 313 | line-height: 35px; } 314 | #light-featured .light-feature-item .light-feature-info p { 315 | font-size: 15px; 316 | display: block; } 317 | #download-app .download-info h3 { 318 | font-size: 25px; 319 | line-height: 38px; } 320 | #download-app .download-info .downlaod-btn a.play-store { 321 | width: 195px; 322 | height: 65px; 323 | line-height: 18px; 324 | margin-right: 0; 325 | margin-bottom: 20px; } 326 | .download-statatics { 327 | text-align: center; 328 | margin-bottom: 15px; } 329 | #client-talk .client-words { 330 | padding: 20px; 331 | margin-bottom: 15px; } 332 | #footer-Content .copyright { 333 | margin-top: 30px; } 334 | .blog-hero-area .contents h2 { 335 | color: #fff; 336 | font-size: 25px; } 337 | .blog-hero-area .contents { 338 | padding: 100px 0 90px; } 339 | #brand-area .brand-subtitle h5 { 340 | font-size: 18px; } 341 | #brand-area { 342 | padding: 50px 0px 60px 0; } 343 | #light-featured { 344 | padding-bottom: 50px; 345 | padding-top: 100px; } 346 | #light-featured .light-feature-item .light-feature-info h4 { 347 | font-size: 18px; } 348 | #light-featured .right-feature-info h3 { 349 | font-size: 20px; 350 | line-height: 30px; } 351 | #light-featured .right-feature-info p { 352 | font-size: 15px; } 353 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 354 | padding-top: 50px; } 355 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 356 | height: 81%; 357 | width: 80%; } 358 | #footer-Content h3.block-title { 359 | margin-bottom: 18px; 360 | margin-top: 15px; } 361 | .back-to-top { 362 | display: none !important; } } 363 | 364 | @media (min-width: 375px) and (max-width: 667px) { 365 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 366 | height: 83%; 367 | width: 83%; } 368 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 369 | padding-top: 68px; } 370 | #pricing .pricing-table h1 span { 371 | left: 16%; } } 372 | 373 | @media (min-width: 414px) and (max-width: 736px) { 374 | #showcase .showcase-slider .screenshot-thumb .fancy-table { 375 | padding-top: 80px; } 376 | #showcase .showcase-slider .screenshot-thumb > .hover-content { 377 | height: 85%; 378 | width: 85%; } 379 | #pricing .pricing-table h1 span { 380 | left: 20%; } } 381 | 382 | @media only screen and (min-width: 992px) and (max-width: 1280px) { 383 | .navbar-expand-md .navbar-nav .nav-link { 384 | font-size: 15px; 385 | padding: 8px 0px; 386 | margin-left: 18px; } 387 | .hero-area .contents h2 { 388 | font-size: 38px; } 389 | #light-featured .right-feature-info h3 { 390 | font-size: 29px; 391 | font-weight: 600; 392 | margin-bottom: 20px; 393 | line-height: 40px; 394 | margin-top: 0; } 395 | #light-featured .light-feature-item .light-feature-icon { 396 | margin-bottom: 15px; } 397 | #light-featured { 398 | padding-bottom: 100px; 399 | padding-top: 100px; } 400 | .section { 401 | padding: 100px 0px; } 402 | .feature-info { 403 | width: 80%; } 404 | .feature-info { 405 | width: 68%; } 406 | #team .single-team .team-social-icons { 407 | bottom: 27%; } 408 | #blog .blog-item-wrapper .blog-item-text { 409 | padding: 20px 15px 15px 15px; } 410 | #blog .blog-item-wrapper h3 { 411 | font-size: 18px; } 412 | #blog .blog-item-wrapper a.read-more { 413 | font-size: 12px; } 414 | #footer-Content { 415 | padding-top: 100px; } 416 | #footer-Content .copyright { 417 | margin-top: 100px; } 418 | .hero-area .contents p { 419 | color: #fff; 420 | font-size: 16px; 421 | line-height: 26px; } 422 | .hero-area2 .contents p { 423 | color: #fff; 424 | font-size: 16px; 425 | line-height: 26px; } 426 | #business-plane .business-item-info h3 { 427 | font-size: 29px; } 428 | .showcase-area .pr-0, .px-0 { 429 | padding-right: 15px !important; } 430 | #showcase { 431 | padding-bottom: 280px; } 432 | #pricing .pricing-table h1 span { 433 | left: 10%; } 434 | #pricing2 .pricing-table h1 span { 435 | left: 10%; } 436 | #testimonial #client-testimonial .content-inner p { 437 | font-size: 20px; 438 | color: #5e629c; 439 | font-style: italic; 440 | line-height: 33px; } } 441 | -------------------------------------------------------------------------------- /client/public/js/nivo-lightbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Nivo Lightbox v1.3.1 3 | * http://dev7studios.com/nivo-lightbox 4 | * 5 | * Copyright 2013, Dev7studios 6 | * Free to use and abuse under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | ;(function($, window, document, undefined){ 11 | 12 | var pluginName = 'nivoLightbox', 13 | defaults = { 14 | effect: 'fade', 15 | theme: 'default', 16 | keyboardNav: true, 17 | clickImgToClose: false, 18 | clickOverlayToClose: true, 19 | onInit: function(){}, 20 | beforeShowLightbox: function(){}, 21 | afterShowLightbox: function(lightbox){}, 22 | beforeHideLightbox: function(){}, 23 | afterHideLightbox: function(){}, 24 | beforePrev: function(element){}, 25 | onPrev: function(element){}, 26 | beforeNext: function(element){}, 27 | onNext: function(element){}, 28 | errorMessage: 'The requested content cannot be loaded. Please try again later.' 29 | }; 30 | 31 | function NivoLightbox(element, options){ 32 | this.el = element; 33 | this.$el = $(this.el); 34 | 35 | this.options = $.extend({}, defaults, options); 36 | 37 | this._defaults = defaults; 38 | this._name = pluginName; 39 | 40 | this.init(); 41 | } 42 | 43 | NivoLightbox.prototype = { 44 | 45 | init: function(){ 46 | var $this = this; 47 | 48 | // Need this so we don't use CSS transitions in mobile 49 | if(!$('html').hasClass('nivo-lightbox-notouch')) $('html').addClass('nivo-lightbox-notouch'); 50 | if('ontouchstart' in document) $('html').removeClass('nivo-lightbox-notouch'); 51 | 52 | // Setup the click 53 | this.$el.on('click', function(e){ 54 | $this.showLightbox(e); 55 | }); 56 | 57 | // keyboardNav 58 | if(this.options.keyboardNav){ 59 | $('body').off('keyup').on('keyup', function(e){ 60 | var code = (e.keyCode ? e.keyCode : e.which); 61 | // Escape 62 | if(code == 27) $this.destructLightbox(); 63 | // Left 64 | if(code == 37) $('.nivo-lightbox-prev').trigger('click'); 65 | // Right 66 | if(code == 39) $('.nivo-lightbox-next').trigger('click'); 67 | }); 68 | } 69 | 70 | this.options.onInit.call(this); 71 | 72 | }, 73 | 74 | showLightbox: function(e){ 75 | var $this = this, 76 | currentLink = this.$el; 77 | 78 | // Check content 79 | var check = this.checkContent(currentLink); 80 | if(!check) return; 81 | 82 | e.preventDefault(); 83 | this.options.beforeShowLightbox.call(this); 84 | var lightbox = this.constructLightbox(); 85 | if(!lightbox) return; 86 | var content = lightbox.find('.nivo-lightbox-content'); 87 | if(!content) return; 88 | 89 | $('body').addClass('nivo-lightbox-body-effect-'+ this.options.effect); 90 | 91 | this.processContent( content, currentLink ); 92 | 93 | // Nav 94 | if(this.$el.attr('data-lightbox-gallery')){ 95 | var galleryItems = $('[data-lightbox-gallery="'+ this.$el.attr('data-lightbox-gallery') +'"]'); 96 | 97 | $('.nivo-lightbox-nav').show(); 98 | 99 | // Prev 100 | $('.nivo-lightbox-prev').off('click').on('click', function(e){ 101 | e.preventDefault(); 102 | var index = galleryItems.index(currentLink); 103 | currentLink = galleryItems.eq(index - 1); 104 | if(!$(currentLink).length) currentLink = galleryItems.last(); 105 | $.when($this.options.beforePrev.call(this, [ currentLink ])).done(function(){ 106 | $this.processContent(content, currentLink); 107 | $this.options.onPrev.call(this, [ currentLink ]); 108 | }); 109 | }); 110 | 111 | // Next 112 | $('.nivo-lightbox-next').off('click').on('click', function(e){ 113 | e.preventDefault(); 114 | var index = galleryItems.index(currentLink); 115 | currentLink = galleryItems.eq(index + 1); 116 | if(!$(currentLink).length) currentLink = galleryItems.first(); 117 | $.when($this.options.beforeNext.call(this, [ currentLink ])).done(function(){ 118 | $this.processContent(content, currentLink); 119 | $this.options.onNext.call(this, [ currentLink ]); 120 | }); 121 | }); 122 | } 123 | 124 | setTimeout(function(){ 125 | lightbox.addClass('nivo-lightbox-open'); 126 | $this.options.afterShowLightbox.call(this, [ lightbox ]); 127 | }, 1); // For CSS transitions 128 | }, 129 | 130 | checkContent: function( link ) { 131 | var $this = this, 132 | href = link.attr('href'), 133 | video = href.match(/(youtube|youtube-nocookie|youtu|vimeo)\.(com|be)\/(watch\?v=([\w-]+)|([\w-]+))/); 134 | 135 | if(href.match(/\.(jpeg|jpg|gif|png)$/i) !== null){ 136 | return true; 137 | } 138 | // Video (Youtube/Vimeo) 139 | else if(video){ 140 | return true; 141 | } 142 | // AJAX 143 | else if(link.attr('data-lightbox-type') == 'ajax'){ 144 | return true; 145 | } 146 | // Inline HTML 147 | else if(href.substring(0, 1) == '#' && link.attr('data-lightbox-type') == 'inline'){ 148 | return true; 149 | } 150 | // iFrame (default) 151 | else if(link.attr('data-lightbox-type') == 'iframe'){ 152 | return true; 153 | } 154 | 155 | return false; 156 | }, 157 | 158 | processContent: function(content, link){ 159 | var $this = this, 160 | href = link.attr('href'), 161 | video = href.match(/(youtube|youtube-nocookie|youtu|vimeo)\.(com|be)\/(watch\?v=([\w-]+)|([\w-]+))/); 162 | 163 | content.html('').addClass('nivo-lightbox-loading'); 164 | 165 | // Is HiDPI? 166 | if(this.isHidpi() && link.attr('data-lightbox-hidpi')){ 167 | href = link.attr('data-lightbox-hidpi'); 168 | } 169 | 170 | // Image 171 | if(href.match(/\.(jpeg|jpg|gif|png)$/i) !== null){ 172 | var img = $('', { src: href, 'class': 'nivo-lightbox-image-display' }); 173 | img.one('load', function() { 174 | var wrap = $('
'); 175 | wrap.append(img); 176 | content.html(wrap).removeClass('nivo-lightbox-loading'); 177 | 178 | // Vertically center images 179 | wrap.css({ 180 | 'line-height': $('.nivo-lightbox-content').height() +'px', 181 | 'height': $('.nivo-lightbox-content').height() +'px' // For Firefox 182 | }); 183 | $(window).resize(function() { 184 | wrap.css({ 185 | 'line-height': $('.nivo-lightbox-content').height() +'px', 186 | 'height': $('.nivo-lightbox-content').height() +'px' // For Firefox 187 | }); 188 | }); 189 | }).each(function() { 190 | if(this.complete) $(this).load(); 191 | }); 192 | 193 | img.error(function() { 194 | var wrap = $('

'+ $this.options.errorMessage +'

'); 195 | content.html(wrap).removeClass('nivo-lightbox-loading'); 196 | }); 197 | } 198 | // Video (Youtube/Vimeo) 199 | else if(video){ 200 | var src = '', 201 | classTerm = 'nivo-lightbox-video'; 202 | 203 | if(video[1] == 'youtube'){ 204 | src = '//www.youtube.com/embed/'+ video[4]; 205 | classTerm = 'nivo-lightbox-youtube'; 206 | } 207 | if(video[1] == 'youtube-nocookie'){ 208 | src = href; //https://www.youtube-nocookie.com/embed/... 209 | classTerm = 'nivo-lightbox-youtube'; 210 | } 211 | if(video[1] == 'youtu'){ 212 | src = '//www.youtube.com/embed/'+ video[3]; 213 | classTerm = 'nivo-lightbox-youtube'; 214 | } 215 | if(video[1] == 'vimeo'){ 216 | src = '//player.vimeo.com/video/'+ video[3]; 217 | classTerm = 'nivo-lightbox-vimeo'; 218 | } 219 | 220 | if(src){ 221 | var iframeVideo = $('