├── .env.example ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── components ├── CustomerContext.jsx ├── UserContext.jsx └── layout.jsx ├── lib └── redirectTo.js ├── middlewares ├── authentication.js ├── database.js ├── middleware.js └── session.js ├── next.config.js ├── now.json ├── package.json ├── pages ├── _app.jsx ├── api │ ├── authenticate.js │ ├── customer.js │ ├── customer │ │ └── [id].js │ ├── session.js │ ├── user │ │ ├── index.js │ │ └── profilepicture.js │ └── users.js ├── customer │ ├── customerForm.jsx │ ├── index.jsx │ └── listData.jsx ├── index.jsx ├── login.jsx ├── profile │ ├── index.jsx │ └── settings.jsx └── signup.jsx └── utils └── auth.js /.env.example: -------------------------------------------------------------------------------- 1 | # MONGODB_URI= 2 | # DB_NAME= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # package-lock 64 | package-lock.json 65 | yarn.lock 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to nextjs-mongodb-app 2 | 3 | :+1::tada: Thank you for being here. It is people like you that make `nextjs-mongodb-app` great and help shape a better open-source community. 4 | 5 | Following this guideline improves communication and organization, which helps save your and other developers' times and effort in future development. 6 | 7 | ## What `nextjs-mongodb-app` is looking for 8 | 9 | I welcome all contributions from the community. There are many ways to contribute: 10 | 11 | - :art: Submit PR to fix bugs, enhance/add existed/new features. 12 | - :children_crossing: Submit bug reports and feature requests. 13 | - :pencil: Improve documentation and writing examples. 14 | 15 | However, please avoid using the issue tracker for support questions. You can receive help on my [Spectrum community](https://spectrum.chat/luvbitstudio). 16 | 17 | ## How to contribute 18 | 19 | ### Bug reports 20 | 21 | If you are submitting a :bug: bug report, please: 22 | - Use a clear and descriptive title. Describe the behavior you observed and the expected behavior. 23 | - Describe the exact steps which reproduce the problem. A minimal reproduction repo is greatly appreciated. 24 | - Include Node version, OS, or other information that may be helpful in the troubleshooting. 25 | 26 | ### Process on submitting a PR 27 | 28 | *Generally, all pull requests should have references to an issue.* 29 | 30 | If you are :sparkles: **adding a new feature** or :zap: **improving an algorithm**, please first [create an issue](../../issues/new) for discussion. 31 | 32 | The steps to submit a PR are: 33 | 34 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. 35 | 36 | 2. Install all dependencies and dev dependencies by `npm install`. 37 | 38 | 3. Make changes and commit (following [commit message styleguides](#commit-message)). 39 | 40 | 4. Make sure your code is linted by running `npm run lint`. 41 | 42 | 5. [Create a pull request](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) 43 | 44 | ## Styleguides 45 | 46 | ### Javascript style 47 | 48 | `nextjs-mongodb-app` follows [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript). Please run `npm run lint` and fix any linting warnings. 49 | 50 | ### Commit message 51 | 52 | - Use the present tense and imperative mood ("Add feature" instead of "Adds feature" or "Added feature") 53 | - Consider starting the commit message with an applicable emoji (ex. [gitmoji](https://gitmoji.carloscuesta.me)) for a more beautiful world :rainbow:. 54 | 55 | :heart: Thank you, 56 | Hoang Vo 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hoang Vo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js + MongoDB + Basic Crud Operation 2 | 3 | A full-fledged app made with Next.JS and MongoDB. 4 | 5 | ## About this project 6 | 7 | `nextjs-mongo-crud` is a continously developed app built with Next.JS and MongoDB. Most tutorials on the Internet are either _half-baked_ or _not production-ready_. This project aims to fix that. 8 | 9 | This project goes even further and attempts to integrate top features as seen in real-life apps. 10 | 11 | Give this project a big ol' 🌟 star motivates me to work on new features. 12 | 13 | Check out my profile (https://tejasrana.com/). 14 | 15 | ## Using this project 16 | 17 | The goal is not to use this project as it, but to implement your own version. 18 | 19 | ### Requirement 20 | 21 | This project relies on the following components. Some of them are **optional** and some may be replaced by others with similar functionalities. 22 | 23 | #### Dependencies 24 | 25 | This project uses the following dependencies: 26 | 27 | - `next.js` - v9 or above required for **API Routes**. 28 | - `react` - v16.8 or above required for **react hooks**. 29 | - `react-dom` - v16.8 or above. 30 | - `mongodb` - may be replaced by `mongoose`. 31 | - `next-connect` - recommended if you want to use Express/Connect middleware. 32 | - `axios`, `axioswal` - optional, may be replaced with any request library. 33 | - `next-session`, `connect-mongo` - may be replaced with any session management solution. 34 | - `bcryptjs` - optional, may be replaced with any password-hashing library. `argon2` recommended. 35 | - `validator` - optional but recommended. 36 | - `formidable` - may be replaced by other file parser. 37 | - `cloudinary` - optional, only if you are using [Cloudinary](https://cloudinary.com) for image upload. 38 | 39 | #### Environmental variables 40 | 41 | The environment variables [will be inlined during build time](https://nextjs.org/docs#build-time-configuration) and thus should not be used in front-end codebase. 42 | 43 | Required environmental variables in this project include: 44 | 45 | - `process.env.MONGODB_URI` The MongoDB Connection String (with credentials) 46 | - `process.env.CLOUDINARY_URL` Cloudinary environment variable for configuration. See [this](https://cloudinary.com/documentation/node_integration#configuration "Cloudinary Configuration"). 47 | - `process.env.DB_NAME` The name of the MongoDB database to be used. 48 | 49 | I include my own MongoDB and Cloudinary environment variables in [.env.example](.env.example) for experimentation purposes. Please replace them with your owns and refrain from sabotaging them. You can use them in development by renaming it into `.env`. 50 | 51 | In production, it is recommended to set the environment variables using the options provided by your cloud/hosting providers. 52 | 53 | ## Development 54 | 55 | `nextjs-mongo-crud` is a long-term developing project. There is no constraint on numbers of features. I continuously accepts feature proposals and am actively developing and expanding functionalities. 56 | 57 | Start the development server by running `yarn dev` or `npm run dev`. 58 | 59 | ### Features 60 | 61 | There are three states in feature development: 62 | 63 | - `developed`: The feature has been fully developed and is functional. 64 | - `developing`: The feature is being developed or being improved. 65 | - `proposed`: The feature is proposed and may or may not be developed in the future. 66 | 67 | #### Authentication 68 | 69 | - Session management 70 | - Allow users to sign up and log in/log out. 71 | 72 | #### User profile 73 | 74 | - Avatar, name, email, location, etc. 75 | - User profile page 76 | - Edit user profile 77 | 78 | #### Social `delayed` 79 | 80 | - Find other users with search functionality 81 | - View other users' profile page 82 | - Add/Remove friends 83 | 84 | #### Account management `developing` 85 | 86 | - Email verification 87 | - Password change 88 | - Password reset 89 | 90 | Have any features in mind, [make an issue](https://github.com/tejasrana95/nextjs-mongo-crud/issues). Would like to work on a feature, [make a PR](https://github.com/tejasrana95/nextjs-mongo-crud/pulls). 91 | 92 | ### Styles 93 | 94 | Despite the look, this project does not contain any stylesheets, and no component has classes. To remove the style, simply remove all ` 63 | 106 | 107 | 108 | Next.js First POC 109 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
121 | 141 |
142 | 143 |
144 | {children} 145 |
146 | 147 | 148 | ); 149 | }; 150 | 151 | export default layout; 152 | -------------------------------------------------------------------------------- /lib/redirectTo.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router'; 2 | 3 | export default function redirectTo(destination, { res, status } = {}) { 4 | if (res) { 5 | res.writeHead(status || 302, { Location: destination }); 6 | res.end(); 7 | } else if (destination[0] === '/' && destination[1] !== '/') { 8 | Router.push(destination); 9 | } else { 10 | window.location = destination; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /middlewares/authentication.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from 'mongodb'; 2 | 3 | export default function authentication(req, res, next) { 4 | if (req.session.userId) { 5 | return req.db.collection('users').findOne(ObjectId(req.session.userId)) 6 | .then((user) => { 7 | if (user) req.user = user; 8 | return next(); 9 | }); 10 | } 11 | return next(); 12 | } 13 | -------------------------------------------------------------------------------- /middlewares/database.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from 'mongodb'; 2 | 3 | const client = new MongoClient(process.env.MONGODB_URI, { 4 | useNewUrlParser: true, 5 | useUnifiedTopology: true, 6 | }); 7 | 8 | export default function database(req, res, next) { 9 | if (!client.isConnected()) { 10 | return client.connect().then(() => { 11 | req.dbClient = client; 12 | req.db = client.db(process.env.DB_NAME); 13 | return next(); 14 | }); 15 | } 16 | req.dbClient = client; 17 | req.db = client.db(process.env.DB_NAME); 18 | return next(); 19 | } 20 | -------------------------------------------------------------------------------- /middlewares/middleware.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import database from './database'; 3 | import session from './session'; 4 | import authentication from './authentication'; 5 | 6 | const middleware = nextConnect(); 7 | 8 | middleware.use(database); 9 | middleware.use(session); 10 | middleware.use(authentication); 11 | 12 | export default middleware; 13 | -------------------------------------------------------------------------------- /middlewares/session.js: -------------------------------------------------------------------------------- 1 | import session from 'next-session'; 2 | import connectMongo from 'connect-mongo'; 3 | 4 | const MongoStore = connectMongo(session); 5 | 6 | export default function (req, res, next) { 7 | return session({ store: new MongoStore({ client: req.dbClient }) })(req, res, next); 8 | } 9 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | module.exports = { 4 | env: { 5 | MONGODB_URI: process.env.MONGODB_URI, 6 | CLOUDINARY_URL: process.env.CLOUDINARY_URL, 7 | DB_NAME: process.env.DB_NAME, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-mongo-crud", 3 | "version": 1, 4 | "build": { 5 | "env": { 6 | "MONGODB_URI": "@mongodb_uri", 7 | "CLOUDINARY_URL": "@cloudinary_url", 8 | "DB_NAME": "@db_name" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-mongo-crud", 3 | "version": "1.0.0", 4 | "description": "Full fledged app made with Next.JS and MongoDB", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tejasrana95/nextjs-mongo-crud.git" 14 | }, 15 | "author": "Tejas Rana (https://www.tejasrana.com)", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/tejasrana95/nextjs-mongo-crud/issues" 19 | }, 20 | "homepage": "https://github.com/tejasrana95/nextjs-mongo-crud#readme", 21 | "dependencies": { 22 | "axios": "^0.21.1", 23 | "axioswal": "^1.0.1", 24 | "bcryptjs": "^2.4.3", 25 | "cloudinary": "^1.25.1", 26 | "connect-mongo": "^4.4.1", 27 | "dotenv": "^8.2.0", 28 | "express-session": "^1.17.1", 29 | "formidable": "^1.2.2", 30 | "js-cookie": "^2.2.1", 31 | "jsonwebtoken": "^8.5.1", 32 | "mongodb": "^3.6.5", 33 | "next": "^10.0.9", 34 | "next-connect": "^0.10.1", 35 | "next-cookies": "^2.0.3", 36 | "next-session": "^3.4.0", 37 | "react": "^17.0.2", 38 | "react-dom": "^17.0.2", 39 | "validator": "^13.5.2" 40 | }, 41 | "devDependencies": { 42 | "eslint": "^7.23.0", 43 | "eslint-config-airbnb": "^18.2.1", 44 | "eslint-plugin-import": "^2.22.1", 45 | "eslint-plugin-jsx-a11y": "^6.4.1", 46 | "eslint-plugin-react": "^7.23.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pages/_app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from 'next/app'; 3 | // import { UserContextProvider } from '../components/UserContext'; 4 | 5 | class MyApp extends App { 6 | render() { 7 | const { Component, pageProps } = this.props; 8 | return ( 9 | // 10 | 11 | // 12 | 13 | ); 14 | } 15 | } 16 | 17 | export default MyApp; 18 | -------------------------------------------------------------------------------- /pages/api/authenticate.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import bcrypt from 'bcryptjs'; 3 | import middleware from '../../middlewares/middleware'; 4 | import jwt from 'jsonwebtoken'; 5 | const handler = nextConnect(); 6 | 7 | handler.use(middleware); 8 | 9 | handler.post((req, res) => { 10 | const { email, password } = req.body; 11 | 12 | return req.db 13 | .collection('users') 14 | .findOne({ email }) 15 | .then((user) => { 16 | if (user) { 17 | return bcrypt.compare(password, user.password).then((result) => { 18 | if (result) return Promise.resolve(user); 19 | return Promise.reject(Error('The password you entered is incorrect')); 20 | }); 21 | } 22 | return Promise.reject(Error('The email does not exist')); 23 | }) 24 | .then((user) => { 25 | req.session.userId = user._id; 26 | delete user.password; 27 | const token = jwt.sign({ "email": user.email, "_id": user._id }, process.env.jwtSecret, { expiresIn: 86400 }) // 1 day token 28 | return res.send({ 29 | status: 'ok', 30 | userData: user, 31 | token: token 32 | }); 33 | }) 34 | .catch(error => res.send({ 35 | status: 'error', 36 | message: error.toString(), 37 | })); 38 | }); 39 | 40 | export default handler; 41 | -------------------------------------------------------------------------------- /pages/api/customer.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import isEmail from 'validator/lib/isEmail'; 3 | import middleware from '../../middlewares/middleware'; 4 | 5 | const handler = nextConnect(); 6 | 7 | handler.use(middleware); 8 | 9 | handler.post((req, res) => { 10 | if (!req.user) return res.status(401).send('You need to be logged in.'); 11 | const { email, name, phone } = req.body; 12 | if (!isEmail(email)) { 13 | return res.status(400).send({ 14 | status: 'error', 15 | message: 'The email you entered is invalid.', 16 | }); 17 | } 18 | return req.db 19 | .collection('customers') 20 | .countDocuments({ email }) 21 | .then((count) => { 22 | if (count) { 23 | return Promise.reject( 24 | res.status(200).send({ 25 | status: 'error', 26 | message: 'The customer with email has already been used', 27 | }) 28 | ); 29 | } 30 | }) 31 | .then(() => req.db.collection('customers').insertOne({ 32 | email, 33 | phone, 34 | name 35 | })) 36 | .then((user) => { 37 | req.session.userId = user.insertedId; 38 | res.status(201).send({ 39 | status: 'ok', 40 | message: 'Customer added successfully', 41 | }); 42 | }) 43 | .catch(error => res.send({ 44 | status: 'error', 45 | message: error.toString(), 46 | })); 47 | }); 48 | 49 | 50 | handler.get((req, res) => { 51 | if (!req.user) return res.status(401).send('You need to be logged in.'); 52 | return req.db 53 | .collection('customers') 54 | .find({}) 55 | .toArray() 56 | .then((data) => res.json(data)) 57 | .catch(error => { 58 | return Promise.reject( 59 | res.status(200).send({ 60 | status: 'error', 61 | message: error.error, 62 | }) 63 | ) 64 | }); 65 | }); 66 | 67 | export default handler; 68 | -------------------------------------------------------------------------------- /pages/api/customer/[id].js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import middleware from '../../../middlewares/middleware'; 3 | import mongodb from 'mongodb'; 4 | import isEmail from 'validator/lib/isEmail'; 5 | const handler = nextConnect(); 6 | 7 | handler.use(middleware); 8 | 9 | 10 | const deleteCustomer = async (req) => { 11 | const { query: { id } } = req; 12 | const query = { _id: new mongodb.ObjectID(id) }; 13 | return await req.db.collection('customers') 14 | .deleteOne(query).then(() => { 15 | return { status: 'success' }; 16 | }).catch(error => { 17 | return Promise.reject({ status: error.toString() }); 18 | }); 19 | } 20 | 21 | const getByID = async (req) => { 22 | const { query: { id } } = req; 23 | const query = { _id: new mongodb.ObjectID(id) }; 24 | 25 | return await req.db 26 | .collection('customers') 27 | .findOne(query) 28 | .then((data) => { 29 | return data 30 | }) 31 | .catch(error => { 32 | return Promise.reject( 33 | { 34 | status: 'error', 35 | message: error.error, 36 | } 37 | ) 38 | }); 39 | } 40 | 41 | const checkEmailExists = async (req, email) => { 42 | 43 | return await req.db 44 | .collection('customers') 45 | .countDocuments({ email }) 46 | .then((count) => { 47 | return count 48 | }) 49 | .catch(error => { 50 | return Promise.reject( 51 | { 52 | status: 'error', 53 | message: error.error, 54 | } 55 | ) 56 | }); 57 | } 58 | 59 | const updateUser = async (req) => { 60 | const { _id, email, name, phone } = req.body; 61 | return await req.db 62 | .collection('customers') 63 | .updateOne( 64 | { _id: new mongodb.ObjectID(_id) }, 65 | { $set: { name, email, phone } } 66 | ).then(response => { 67 | return { status: 'ok' } 68 | }).catch(error => { 69 | return { 70 | status: 'error', 71 | message: error.toString(), 72 | } 73 | }); 74 | 75 | } 76 | 77 | handler.delete((req, res) => { 78 | if (!req.user) return res.status(401).send('You need to be logged in.'); 79 | return deleteCustomer(req).then(data => { 80 | return res.status(204).send({}); 81 | }).catch(error => { 82 | return Promise.reject( 83 | res.status(200).send({ 84 | status: error.toString() 85 | }) 86 | ) 87 | }); 88 | }); 89 | 90 | handler.put((req, res) => { 91 | if (!req.user) return res.status(401).send('You need to be logged in.'); 92 | const { email } = req.body; 93 | if (!isEmail(email)) { 94 | return res.status(400).send({ 95 | status: 'error', 96 | message: 'The email you entered is invalid.', 97 | }); 98 | } 99 | 100 | return getByID(req).then(data => { 101 | 102 | if (data && data.email === email) { 103 | return updateUser(req).then(data => { 104 | return res.status(201).send({ 105 | status: 'ok', 106 | message: 'Customer update successfully', 107 | }) 108 | }).catch(error => { 109 | return res.status(200).send(error); 110 | }); 111 | } else { 112 | return checkEmailExists(req, email).then(count => { 113 | if (count > 0) { 114 | return res.status(400).send({ 115 | status: 'error', 116 | message: "Email already exists", 117 | }); 118 | } else { 119 | return updateUser(req).then(data => { 120 | res.status(201).send({ 121 | status: 'ok', 122 | message: 'Customer update successfully', 123 | }) 124 | }).catch(error => { 125 | res.status(200).send(error); 126 | }); 127 | } 128 | }).catch(error => { 129 | res.status(200).send(error); 130 | }); 131 | } 132 | }) 133 | 134 | }); 135 | 136 | handler.get((req, res) => { 137 | if (!req.user) return res.status(401).send('You need to be logged in.'); 138 | return getByID(req).then(data => { 139 | return res.json(data); 140 | }).catch(error => { 141 | return Promise.reject( 142 | res.status(200).send({ 143 | status: error.toString() 144 | }) 145 | ) 146 | }); 147 | }); 148 | 149 | 150 | export default handler; 151 | -------------------------------------------------------------------------------- /pages/api/session.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import middleware from '../../middlewares/middleware'; 3 | 4 | const handler = nextConnect(); 5 | 6 | handler.use(middleware); 7 | 8 | handler.get((req, res) => { 9 | if (req.user) { 10 | const { 11 | name, email, bio, profilePicture, gender 12 | } = req.user; 13 | return res.status(200).send({ 14 | status: 'ok', 15 | data: { 16 | isLoggedIn: true, 17 | user: { 18 | name, email, bio, profilePicture, gender 19 | }, 20 | }, 21 | }); 22 | } 23 | return res.status(200).send({ 24 | status: 'ok', 25 | data: { 26 | isLoggedIn: false, 27 | user: {}, 28 | }, 29 | }); 30 | }); 31 | 32 | handler.delete((req, res) => { 33 | delete req.session.userId; 34 | return res.status(200).send({ 35 | status: 'ok', 36 | message: 'You have been logged out.', 37 | }); 38 | }); 39 | 40 | export default handler; 41 | -------------------------------------------------------------------------------- /pages/api/user/index.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import middleware from '../../../middlewares/middleware'; 3 | 4 | const handler = nextConnect(); 5 | 6 | handler.use(middleware); 7 | 8 | handler.patch((req, res) => { 9 | if (!req.user) return res.status(401).send('You need to be logged in.'); 10 | const { name, bio } = req.body; 11 | return req.db 12 | .collection('users') 13 | .updateOne({ _id: req.user._id }, { $set: { name, bio } }) 14 | .then(() => res.json({ 15 | message: 'Profile updated successfully', 16 | data: { name, bio }, 17 | })) 18 | .catch(error => res.send({ 19 | status: 'error', 20 | message: error.toString(), 21 | })); 22 | }); 23 | 24 | export default handler; 25 | -------------------------------------------------------------------------------- /pages/api/user/profilepicture.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import formidable from 'formidable'; 3 | import { v2 as cloudinary } from 'cloudinary'; 4 | import middleware from '../../../middlewares/middleware'; 5 | 6 | const handler = nextConnect(); 7 | 8 | handler.use(middleware); 9 | 10 | handler.put((req, res) => { 11 | if (!req.user) return res.status(401).send('You need to be logged in.'); 12 | const form = new formidable.IncomingForm(); 13 | return form.parse(req, (err, fields, files) => cloudinary.uploader 14 | .upload(files.profilePicture.path, { 15 | width: 512, 16 | height: 512, 17 | crop: 'fill', 18 | }) 19 | .then(image => req.db 20 | .collection('users') 21 | .updateOne( 22 | { _id: req.user._id }, 23 | { $set: { profilePicture: image.secure_url } }, 24 | )) 25 | .then(() => res.send({ 26 | status: 'success', 27 | message: 'Profile picture updated successfully', 28 | })) 29 | .catch(error => res.send({ 30 | status: 'error', 31 | message: error.toString(), 32 | }))); 33 | }); 34 | 35 | export const config = { 36 | api: { 37 | bodyParser: false, 38 | }, 39 | }; 40 | 41 | export default handler; 42 | -------------------------------------------------------------------------------- /pages/api/users.js: -------------------------------------------------------------------------------- 1 | import nextConnect from 'next-connect'; 2 | import isEmail from 'validator/lib/isEmail'; 3 | import bcrypt from 'bcryptjs'; 4 | import middleware from '../../middlewares/middleware'; 5 | 6 | const handler = nextConnect(); 7 | 8 | handler.use(middleware); 9 | 10 | handler.post((req, res) => { 11 | const { email, name, password, gender } = req.body; 12 | if (!isEmail(email)) { 13 | return res.send({ 14 | status: 'error', 15 | message: 'The email you entered is invalid.', 16 | }); 17 | } 18 | return req.db 19 | .collection('users') 20 | .countDocuments({ email }) 21 | .then((count) => { 22 | if (count) { 23 | return Promise.reject(Error('The email has already been used.')); 24 | } 25 | return bcrypt.hashSync(password); 26 | }) 27 | .then(hashedPassword => req.db.collection('users').insertOne({ 28 | email, 29 | password: hashedPassword, 30 | name, 31 | gender 32 | })) 33 | .then((user) => { 34 | req.session.userId = user.insertedId; 35 | res.status(201).send({ 36 | status: 'ok', 37 | message: 'User signed up successfully', 38 | }); 39 | }) 40 | .catch(error => res.send({ 41 | status: 'error', 42 | message: error.toString(), 43 | })); 44 | }); 45 | 46 | export default handler; 47 | -------------------------------------------------------------------------------- /pages/customer/customerForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import axios from 'axios'; 3 | import Layout from '../../components/layout'; 4 | import Swal from 'sweetalert2'; 5 | class CustomerForm extends React.Component { 6 | 7 | constructor(props) { 8 | 9 | super(props); 10 | 11 | this.initialState = { 12 | _id: '', 13 | name: '', 14 | email: '', 15 | phone: '' 16 | } 17 | 18 | this.state = { 19 | _id: '', 20 | name: '', 21 | email: '', 22 | phone: '' 23 | } 24 | 25 | this.handleSubmit = this.handleSubmit.bind(this); 26 | } 27 | 28 | UNSAFE_componentWillReceiveProps(nextProps) { 29 | if (nextProps.edit !== null) { 30 | this.getById(nextProps.edit); 31 | } 32 | } 33 | 34 | getById(id) { 35 | axios.get('/api/customer/' + id).then((data) => { 36 | this.setState({ 37 | _id: data.data._id, 38 | name: data.data.name, 39 | email: data.data.email, 40 | phone: data.data.phone 41 | }); 42 | 43 | }).catch(error => { 44 | console.error(error); 45 | }); 46 | return; 47 | } 48 | 49 | handleSubmit(event) { 50 | event.preventDefault(); 51 | if (this.state._id === '') { 52 | this.createSubmit(); 53 | } else { 54 | this.updateSubmit(); 55 | } 56 | } 57 | 58 | 59 | createSubmit() { 60 | axios.post('/api/customer', this.state).then((data) => { 61 | 62 | if (data.statusText === 'Created') { 63 | Swal.fire({ 64 | type: 'success', 65 | title: 'Success', 66 | text: 'Customer Added successfully', 67 | timer: 1000, 68 | toast: true 69 | }); 70 | this.cancelCustomer(); 71 | } else { 72 | Swal.fire({ 73 | type: 'error', 74 | title: 'Error', 75 | text: data.data.message, 76 | timer: 3000 77 | }) 78 | } 79 | this.props.updateList(new Date().getTime()); 80 | }); 81 | return; 82 | } 83 | 84 | 85 | updateSubmit() { 86 | axios.put('/api/customer/' + this.state._id, this.state).then((data) => { 87 | Swal.fire({ 88 | type: 'success', 89 | title: 'Success', 90 | text: 'Customer updated successfully', 91 | timer: 1000, 92 | toast: true 93 | }); 94 | this.cancelCustomer(); 95 | this.props.updateList(new Date().getTime()); 96 | }).catch(error => { 97 | Swal.fire({ 98 | type: 'error', 99 | title: 'Error', 100 | text: error.response.data.message, 101 | timer: 3000, 102 | toast: false 103 | }); 104 | }); 105 | return; 106 | } 107 | 108 | cancelCustomer = () => { 109 | setTimeout(() => { 110 | this.setState(this.initialState); 111 | }, 1000); 112 | 113 | } 114 | 115 | componentDidMount() { 116 | this.setState(this.initialState); 117 | } 118 | 119 | render() { 120 | return ( 121 | <> 122 | 129 |
130 |
131 |
132 |
133 | 137 |
138 |
139 | 142 |
143 |
144 | 147 |
148 |
149 | 150 |
151 | 152 |
153 | 154 | ) 155 | } 156 | } 157 | 158 | export default CustomerForm; 159 | -------------------------------------------------------------------------------- /pages/customer/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import axios from 'axios'; 3 | import Layout from '../../components/layout'; 4 | import { UserContext } from '../../components/UserContext'; 5 | import Swal from 'sweetalert2'; 6 | import TableExp from './listData'; 7 | import CustomerForm from './customerForm'; 8 | import redirectTo from '../../lib/redirectTo'; 9 | import { withAuthSync } from '../../utils/auth'; 10 | 11 | class CustomerPage extends React.Component { 12 | updateTimeStamp = {}; 13 | static contextType = UserContext; 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | updateTimeStamp: 0, 18 | id: null 19 | }; 20 | this.updateList = this.updateList.bind(this); 21 | this.edit = this.edit.bind(this); 22 | } 23 | 24 | 25 | 26 | updateList(updateTime) { 27 | this.setState({ updateTimeStamp: updateTime }); 28 | } 29 | 30 | componentDidMount() { 31 | // setTimeout(() => { 32 | // if (!this.context.state.isLoggedIn) { 33 | // redirectTo('/login'); 34 | // } 35 | // }, 2000); 36 | 37 | } 38 | 39 | edit(id) { 40 | this.setState({ id: id }); 41 | } 42 | 43 | render() { 44 | 45 | return ( 46 | 47 | 56 |

Customer

57 |
58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 | ); 67 | } 68 | } 69 | 70 | export default withAuthSync(CustomerPage); 71 | 72 | -------------------------------------------------------------------------------- /pages/customer/listData.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import Swal from 'sweetalert2'; 4 | class TableExp extends React.Component { 5 | 6 | constructor(props) { 7 | 8 | super(); 9 | this.state = { 10 | tableData: [{ 11 | srNo: '', 12 | _id: '', 13 | name: '', 14 | email: '', 15 | phone: '', 16 | 17 | }], 18 | lastUpdate: 0 19 | }; 20 | } 21 | 22 | UNSAFE_componentWillReceiveProps(nextProps) { 23 | if (this.state.lastUpdate !== nextProps.update) { 24 | this.componentDidMount(); 25 | this.setState({ lastUpdate: nextProps.update }); 26 | } 27 | } 28 | 29 | componentDidMount() { 30 | axios.get('/api/customer', { 31 | responseType: 'json' 32 | }).then(response => { 33 | this.setState({ tableData: response.data }); 34 | }); 35 | } 36 | 37 | edit(id) { 38 | this.props.edit(id); 39 | } 40 | 41 | delete(id) { 42 | Swal.fire({ 43 | type: 'question', 44 | title: 'Are you sure', 45 | text: "Are you sure to delete this?", 46 | timer: 3000, 47 | showCancelButton: true 48 | }).then(data => { 49 | if (data.value === true) { 50 | this.deleteApi(id) 51 | } 52 | }) 53 | } 54 | 55 | deleteApi(id) { 56 | axios.delete('/api/customer/' + id, { 57 | responseType: 'json' 58 | }).then(response => { 59 | this.componentDidMount(); 60 | }); 61 | 62 | } 63 | 64 | render() { 65 | 66 | const { tableData } = this.state; 67 | return ( 68 | <> 69 | 70 | 85 |

List

86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | {tableData.map((subData, index) => ( 100 | 101 | 102 | 103 | 104 | 105 | 106 | 116 | 117 | 118 | ))} 119 | 120 |
Sr.NoIDNameEmailPhoneAction
{index + 1}{subData._id}{subData.name}{subData.email}{subData.phone} 107 | this.edit(subData._id)} > 108 | 109 | 110 |   111 | this.delete(subData._id)} > 112 | 113 | 114 | 115 |
121 |
122 | 123 | 124 | ); 125 | } 126 | } 127 | 128 | export default TableExp; -------------------------------------------------------------------------------- /pages/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | // import { UserContext } from '../components/UserContext'; 3 | // import Layout from '../components/layout'; 4 | 5 | const IndexPage = () => { 6 | // const { state: { isLoggedIn, user: { name } } } = useContext(UserContext); 7 | return ( 8 | // 9 | 10 | 11 | // 12 |
13 | 31 |

32 | Hello, 33 | {/* {' '} 34 | {(isLoggedIn ? name : 'stranger')} 35 | ! */} 36 |

37 |

Have a wonderful day.

38 |
39 |

40 | To start using this starter kit, please uncomment every commented line in _app.jsx and index.jsx. 41 |
42 | Then the screen show you an error code: 500 in /api/session. 43 |
44 | Don't panic, please just configure the connection with your database in .env file and you can connect with your choice of database. 45 |

46 |
47 |
48 | ); 49 | }; 50 | 51 | export default IndexPage; 52 | -------------------------------------------------------------------------------- /pages/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import axioswal from 'axioswal'; 3 | import { UserContext } from '../components/UserContext'; 4 | import Layout from '../components/layout'; 5 | import redirectTo from '../lib/redirectTo'; 6 | import { login } from '../utils/auth' 7 | const LoginPage = () => { 8 | const { dispatch } = useContext(UserContext); 9 | const [email, setEmail] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | 12 | const handleSubmit = (event) => { 13 | event.preventDefault(); 14 | axioswal 15 | .post('/api/authenticate', { 16 | email, 17 | password, 18 | }) 19 | .then((data) => { 20 | if (data.status === 'ok') { 21 | const token = data.token; 22 | login(token, data.userData); 23 | } 24 | }); 25 | }; 26 | 27 | return ( 28 | 29 |
30 |

Log in

31 |
32 |
33 | setEmail(e.target.value)} 38 | /> 39 |
40 |
41 | setPassword(e.target.value)} 46 | /> 47 |
48 | 51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | export default LoginPage; 58 | -------------------------------------------------------------------------------- /pages/profile/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import Link from 'next/link'; 3 | import Layout from '../../components/layout'; 4 | import { withAuthSync, getUserData } from '../../utils/auth'; 5 | 6 | 7 | class ProfilePage extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | getUser: {} 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | this.setState({ getUser: getUserData() }); 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 | 32 |

Profile

33 |
34 | {this.state.getUser.name} 35 |

36 | Name: 37 | {' '} 38 | {this.state.getUser.name} 39 |

40 |

41 | Bio: 42 | {' '} 43 | {this.state.getUser.bio} 44 |

45 |

46 | Email: 47 | {' '} 48 | {this.state.getUser.email} 49 |

50 |

51 | Gender: 52 | {' '} 53 | {this.state.getUser.gender} 54 |

55 |
56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | export default withAuthSync(ProfilePage); -------------------------------------------------------------------------------- /pages/profile/settings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import axioswal from 'axioswal'; 3 | import { UserContext } from '../../components/UserContext'; 4 | import Layout from '../../components/layout'; 5 | 6 | const ProfileSection = ({ user: { name: initialName, bio: initialBio }, dispatch }) => { 7 | const [name, setName] = useState(initialName); 8 | const [bio, setBio] = useState(initialBio); 9 | 10 | const handleSubmit = (event) => { 11 | event.preventDefault(); 12 | axioswal 13 | .patch( 14 | '/api/user', 15 | { name, bio }, 16 | ) 17 | .then(() => { 18 | dispatch({ type: 'fetch' }); 19 | }); 20 | }; 21 | 22 | const profilePictureRef = React.createRef(); 23 | const [isUploading, setIsUploading] = useState(false); 24 | 25 | const handleSubmitProfilePicture = (event) => { 26 | if (isUploading) return; 27 | event.preventDefault(); 28 | setIsUploading(true); 29 | const formData = new FormData(); 30 | formData.append('profilePicture', profilePictureRef.current.files[0]); 31 | axioswal 32 | .put('/api/user/profilepicture', formData) 33 | .then(() => { 34 | setIsUploading(false); 35 | dispatch({ type: 'fetch' }); 36 | }); 37 | }; 38 | 39 | return ( 40 | <> 41 | 48 |
49 |

Edit Profile

50 |
51 | 62 |