Open In App

Customer Relationship Management (CRM) System with Node.js and Express.js

Last Updated : 25 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

CRM systems are important tools for businesses to manage their customer interactions, both with existing and potential clients. In this article, we will demonstrate how to create a CRM system using Node.js and Express. We will cover the key functionalities, prerequisites, approach, and steps required to create the project.

Output Preview:

ReactApp-GoogleChrome2024-04-1209-40-51-ezgifcom-video-to-gif-converter

Preview of the Project

Prerequisites:

Functionalities

User Authentication and Authorization

  • Companies that want to use the CRM Portal can register on the application and access the functionalities by signing in.
  • JWT, JsonWebToken and bcryptjs are some packages used for implementing smooth authorization.
  • Context API helps to maintain the state of the user that has logged in.

Adding Customers

  • The company can easily add new customers and details about them.
  • A form is created with the post method to implement this functionality. Status can be selected from a drop down.
  • The page navigates to the all customers page when a customer is created.

Customer Management:

  • Implementing functionalities to manage customer data such as name, email, phone number, and address. Allow users to add, view, update, and delete customer records.

Steps to Setup the Backend of Application

Step 1: Create a new directory named backend.

mkdir server
cd server

Step 2: Create a server using the following command in your terminal.

npm init -y

Step 3: Install the necessary package in your server using the following command.

npm install bcryptjs cors dotenv express 
npm install cookie-parser jsonwebtoken
npm install mongodb mongoose morgan helmet nodemon

Step 4: Create a .env file to store things you don’t want to show to others.

MONGO = Mention Yours
JWT = jbckjbwd3487238dsnbfkj

Project Structure:

Screenshot-2024-04-12-091346

Backend Folder Structure

The updated Dependencies in package.json file of backend will look like:

"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.4",
"express": "^4.18.2",
"cookie-parser": "^1.4.6",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.3.0",
"mongoose": "^8.1.3",
"morgan": "^1.10.0",
"helmet": "^7.1.0",
"nodemon": "^3.0.3"
}

Example: Create the required files as shown in folder structure and add the following codes.

JavaScript
// index.js

import express from "express";
import dotenv from "dotenv";
import helmet from "helmet";
import morgan from "morgan";
import mongoose from "mongoose";
import userRoute from "./routes/user.js";
import customerRoute from "./routes/customer.js";
import cookieParser from "cookie-parser";
import cors from "cors"

const app = express();
dotenv.config();

const PORT = process.env.PORT || 7700;

const connect = async () => {
    try {
        await mongoose.connect(process.env.MONGO);
        console.log("Connected to mongoDB.");
    } catch (error) {
        throw error;
    }
};

mongoose.connection.on("disconnected", () => {
    console.log("mongoDB disconnected!");
});

app.get('/',
    (req, res) => { res.send('Hello from Express!') });

//middlewares
app.use(cookieParser())
app.use(express.json());
app.use(helmet());


app.use(cors({
    origin: "http://localhost:3000",
    credentials: true
}))


app.use(morgan("common"));

app.use("/api/users", userRoute);
app.use("/api/customers", customerRoute);

app.listen(PORT, () => {
    console.log("Listening on port 7700");
    connect();
});
JavaScript
// error.js

export const createError = (status, message) => {
    const err = new Error();
    err.status = status;
    err.message = message;
    return err;
  };
JavaScript
// /server/models/User.js

import mongoose from "mongoose"

const UserSchema = new mongoose.Schema(
    {
        username: {type: String, required: true, unique: true},
        password: {type: String, required: true},
        email: { type: String, required: true}
    },
    {
        timestamps: true
    }
)

export default mongoose.model("User", UserSchema);
JavaScript
// models/Customer.js

import mongoose from "mongoose";

const CustomerSchema = new mongoose.Schema({
    name: { type: String, required: true },
    company: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    service: { type: String, required: true },
    email: { type: String, required: true },
    phone: { type: String, required: true },
    status: { type: String, default: "not started" },
}, {
    timestamps: true
})

export default mongoose.model("Customer", CustomerSchema);
JavaScript
// controllers/user.js

import User from "../models/User.js";
import bcrypt from "bcryptjs";
import { createError } from "../error.js";
import jwt from "jsonwebtoken";

export const register = async (req, res, next) => {
    try {

        //check for already exist
        const em = await User.findOne({ email: req.body.email });
        if (em)
            return res.status(409).send({
                message: "User with given email already exists"
            })
        const salt = bcrypt.genSaltSync(10);
        const hash = bcrypt.hashSync(req.body.password, salt);

        const newUser = new User({
            ...req.body,
            password: hash,
        });

        await newUser.save();
        res.status(200).send("User has been created.");
    } catch (err) {
        next(err);
    }
};


export const login = async (req, res, next) => {
    try {
        const user = await User.findOne({
            username: req.body.username
        });
        if (!user) return next(
            createError(404, "User not found!"));

        const isPasswordCorrect = await bcrypt.compare(
            req.body.password,
            user.password
        );
        if (!isPasswordCorrect)
            return next(createError(
                400, "Wrong password or username!"));

        const token = jwt.sign(
            { id: user._id, isAdmin: user.isAdmin },
            process.env.JWT
        );

        const { password, isAdmin, ...otherDetails } = user._doc;
        res
            .cookie("access_token", token, {
                httpOnly: true,
            })
            .status(200)
            .json({ details: { ...otherDetails }, isAdmin });
    } catch (err) {
        next(err);
    }
};
JavaScript
// controllers/customer.js

import Customer from "../models/Customer.js";

export const createCustomer = async (req, res, next) => {
    const newCustomer = new Customer(req.body)

    try {
        const savedCustomer = await newCustomer.save();
        res.status(200).json(savedCustomer);
    }
    catch (err) {
        next(err)
    }
}

export const deleteCustomer = async (req, res, next) => {
    try {
        await Customer.findByIdAndDelete(req.params.id);
        res.status(200).json("the Customer has been deleted");
    } catch (err) {
        next(err);
    }
};

export const getCustomers = async (req, res, next) => {
    const userId = req.params.userId;

    try {
        const customers = await Customer.find({ company: userId });
        res.status(200).json(customers);
    } catch (err) {
        next(err)
    }
}

export const updateCustomer = async (req, res, next) => {

    try {
        const customer = await Customer.findByIdAndUpdate(
            req.params.id,
            { $set: req.body },
            { new: true }
        );
        res.status(200).json(customer);
    } catch (err) {
        next(err);
    }
}
JavaScript
// routes/user.js

import express from "express";
import { login, register } from "../controllers/user.js";

const router = express.Router();

router.post("/register", register)
router.post("/login", login)

export default router;
JavaScript
// routes/customer.js

import express from "express";
import {
    createCustomer,
    deleteCustomer,
    getCustomers,
    updateCustomer,
} from "../controllers/customer.js";

const router = express.Router();

router.post("/", createCustomer);
router.put("/:id", updateCustomer);
router.delete("/:id", deleteCustomer);
router.get("/:userId", getCustomers);

export default router;

Step 6: To start the backend run the following command.

nodemon index.js
Screenshot-2024-04-12-092332

Customers Documents in the Mongodb Backend

Steps to Setup the Frontend of Application

Step 1: Create react application in your project folder using the following command and navigate to the folder.

npx create-react-app client
cd client

Step 2: Install additional packages

npm install axios react-router-dom 
npm install @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

Project Structure:

Screenshot-2024-04-12-083808

Frontend Folder Structure

The updated Dependencies in package.json file of backend will look like:

"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Step 3: Add proxy at the end of the package.json file like so:

  },
"proxy": "http://localhost:7700/api"
}

Example: Implementation to design the frontend of the application.

HTML
<!--client/public/index.html-->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap" rel="stylesheet">
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
   
  </body>
</html>
CSS
/* src/index.css */

body {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: "Manrope", sans-serif;
}
CSS
/* src/styles/login.css */

body,
html {
    overflow-x: hidden;
}

.loginCard {
    background-size: cover;
    background-repeat: no-repeat;
    height: 100vh;
    overflow: hidden;
}

.center {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 400px;
    background-color: lavender;
    border-radius: 10px;
    padding-top: 20px;
}

.center h1 {
    font-size: 1.2rem;
    text-align: center;
    padding: 0 0 20px 0;
    border-bottom: 1px solid silver;
}

.center form {
    padding: 0 40px;
    box-sizing: border-box;
}

form .txt_field {
    position: relative;
    border-bottom: 2px solid #adadad;
    margin: 30px 0;
}

.txt_field {
    width: 100%;
    padding: 0 5px;
    height: 40px;
    font-size: 16px;
    border: none;
    background: none;
    outline: none;
}

.txt_field input {
    width: 100%;
    padding: 0 5px;
    height: 40px;
    font-size: 16px;
    border: none;
    background: none;
    outline: none;
}

.login_button .button {
    width: 100%;
    height: 50px;
    border: none;
    background: transparent;
    border-radius: 25px;
    font-size: 18px;
    color: black;
    font-weight: 700;
    cursor: pointer;
    outline: none;
}

.login_button {
    width: 100%;
    height: 50px;
    border: none;
    background: purple;
    border-radius: 25px;
    font-size: 18px;
    color: white;
    font-weight: 700;
    cursor: pointer;
    outline: none;
}

.signup_link {
    margin: 30px 0;
    text-align: center;
    font-size: 16px;
    color: #666666;
}

.signup_link a {
    color: black;
    text-decoration: none;
}

.signup_link a:hover {
    text-decoration: underline;
}

@media screen and (max-width: 600px) {
    .loginCard .center {
        width: 300px;
    }
}
CSS
/* src/styles/register.css */

body,
html {
    overflow-x: hidden;
}

.registerCard {
    background-size: cover;
    background-repeat: no-repeat;
    height: 1000px;
    overflow: hidden;
    position: relative;
}

.registerCard .center {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 400px;
    background-color: lavender;
    border-radius: 10px;
}

.center h1 {
    font-size: 1.2rem;
    text-align: center;
    padding: 0 0 20px 0;
    border-bottom: 1px solid silver;
}

.center form {
    padding: 0 40px;
    box-sizing: border-box;
}

form .txt_field {
    position: relative;
    border-bottom: 2px solid #adadad;
    margin: 30px 0;
}

form .txt_field_img {
    position: relative;
    margin-top: 10px;
}

.txt_field {
    width: 100%;
    padding: 0 5px;
    height: 40px;
    font-size: 16px;
    border: none;
    background: none;
    outline: none;
}

.txt_field input {
    width: 100%;
    padding: 0 5px;
    height: 40px;
    font-size: 16px;
    border: none;
    background: none;
    outline: none;
}

.registerCard form .image {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-top: 20px;
}

.register input {
    width: 100%;
    height: 50px;
    border: none;
    background: transparent;
    border-radius: 25px;
    font-size: 16px;
    color: black;
    outline: none;
}

.login_button .button {
    width: 100%;
    height: 50px;
    border: none;
    background: transparent;
    border-radius: 25px;
    font-size: 18px;
    color: white;
    font-weight: 700;
    cursor: pointer;
    outline: none;
}

.login_button {
    width: 100%;
    height: 50px;
    border: none;
    background: purple;
    border-radius: 25px;
    font-size: 18px;
    color: white;
    font-weight: 700;
    cursor: pointer;
    outline: none;
}

.signup_link {
    margin: 30px 0;
    text-align: center;
    font-size: 16px;
    color: #666666;
}

.signup_link a {
    color: black;
    text-decoration: none;
}

.signup_link a:hover {
    text-decoration: underline;
}

@media screen and (max-width: 500px) {
    .registerCard .center {
        width: 300px;
        margin: 0;
    }
}
CSS
/* src/styles/custCard.css */

.cust-card {
    width: 40%;
    height: 200px;
    background-color: lavender;
    display: flex;
    gap: 100px;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.cust-card .icon {
    font-size: 30px;
    color: var(--red);
    width: 10%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease-in-out;
}

.cust-card .icon:hover {
    transform: translateY(-2px);
    color: violet;
}

.cust-card .details {
    display: flex;
    flex-direction: column;
    gap: 20px;
    width: 70%;
}

.cust-card .cust-name {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;


}

.cust-card .status {
    padding: 10px 20px;
    text-align: center;
    border: none;
    color: white;
    cursor: pointer;
    font-size: 1.025rem;
    transition: all 0.3s ease;
}


.cust-card .cust-name h1 {
    color: purple;
}

.cust-card .cust-details {
    display: flex;
    gap: 20px;
    align-items: center;
    flex-wrap: wrap;
}

.cust-card .cust-details p {
    font-size: 1.2rem;
    font-weight: 800;
}
CSS
/* src/styles/createCustomer.css */

body,
html {
    overflow-x: hidden;
}

.createCustContainer {
    background-color: lavender;
    height: 100%;
}

.cpContainer {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    width: 100%;
    padding: 50px;
}

.formContainer {
    width: 70%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 100px 0;
    flex-wrap: wrap;
}

.inputContainer {
    width: 80%;
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    gap: 20px;
    justify-content: center;
    align-items: center;

}

.inputContainer button {
    width: 100px;
}



.star-rating-slider {
    margin: 5px 0 15px 0;
}

.star-rating-slider .star-icon {
    cursor: pointer;
    margin: 0 5px;
}

.input {
    display: flex;
    flex-direction: column;
    width: 90%;
}


.createRestContainer .column .input .formInput {
    display: flex;
    flex-direction: column;
    width: 70%;
    gap: 10px;
}

.input label {
    font-size: 1rem;
    font-weight: bold;
}

.inputContainer button {
    width: 200px;
    height: 40px;
    margin-top: 20px;
    border: none;
    background: purple;
    border-radius: 25px;
    font-size: 18px;
    color: white;
    font-weight: 700;
    cursor: pointer;
    outline: none;
}

.input input {
    font-size: 1rem;
    height: 40px;
    width: 100%;
    margin-top: 10px;
    font-size: 1rem;
    padding: 5px 5px 5px 15px;
    outline: 0;
    border: 0;
    border-bottom: 1px solid plum;
    background: white;
    color: purple;
}

.createCustContainer .column .input .type {
    text-align: center;
    height: 35px;
    background: white;
    border-radius: 5px;
    color: violet;
    cursor: pointer;
    font-weight: bold;
}

.createCustContainer select {
    height: 50px;
    font-size: medium;
    font-family: "Manrope", sans-serif;
}

.createCustContainer option {
    font-size: medium;
    font-family: "Manrope", sans-serif;
}

@media screen and (max-width: 800px) {
    .inputContainer {
        width: 80%;
    }

    .picsContainer {
        width: 80%;
    }
}

@media screen and (max-width: 600px) {


    .formContainer {
        width: 90%;
    }

    .inputContainer {
        width: 100%;
    }

    .createCustContainer.column {
        width: 100%;
        margin-top: 50px;

    }

}
CSS
/* src/styles/customers.css */

.cust-container {
    margin-top: 100px;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 50px;
    flex-wrap: wrap;
    
}
CSS
/* src/styles/navbar.css */

* {
    margin: 0;
    padding: 0;
    text-decoration: none;
}

.navContainer {
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background-color: black;
    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
    padding: 0px 7%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    z-index: 100;
}


.navLogo {
    color: white;
    font-family: "Rubik Doodle Shadow", system-ui;
    font-weight: 900;
    font-size: 1.5rem;
    font-style: italic;
}

.navbar ul {
    list-style: none;
}

.navbar ul li {
    position: relative;
    float: left;
}

.profilePicture {
    height: 40px;
    width: 40px;
}

.profilePicture img {
    margin-top: 10px;
    height: 40px;
    width: 40px;
    border-radius: 50%;
    object-fit: cover;
}

#usernamename {
    display: none;
}

.navbar ul li p {
    font-size: 1rem;
    padding: 20px;
    color: white;
    display: block;
    transition: all 1s;
}

.navbar ul li p:hover {
    transform: translateY(-1px);
    border-bottom: solid 2px white;
}

#menu-bar {
    display: none;
}

.navContainer label {
    font-size: 1.5rem;
    color: white;
    cursor: pointer;
    display: none;
}

@media (max-width:800px) {
    .navContainer {
        height: 70px;
    }

    .navContainer label {
        display: initial;
    }

    .navContainer .navbar {
        position: fixed;
        top: 70px;
        left: -100%;
        text-align: center;
        background: white;
        border-top: 1px solid rgba(0, 0, 0, 0.1);
        display: block;
        transition: all 0.3s ease;
        width: 100%;
    }

    .profilePicture {
        display: none;
    }

    #usernamename {
        font-weight: bolder;
        display: block;
    }

    .navbar ul li p {
        color: black;
    }

    .navbar ul li p:hover {
        transform: translateY(-1px);

        border-bottom: none;
    }

    .navbar ul li {
        width: 100%;
    }

    #menu-bar:checked~.navbar {
        left: 0;
    }
}
JavaScript
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthContextProvider } from './authContext';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <AuthContextProvider>
        <React.StrictMode>
            <App />
        </React.StrictMode>
    </AuthContextProvider>
);
JavaScript
// src/App.js

import { BrowserRouter, Routes, Route } from "react-router-dom"
import { useContext } from "react";
import { AuthContext } from "./authContext";
import Customers from "./pages/Customers";
import Login from "./pages/Login";
import Register from "./pages/Register";
import CreateCustomer from "./pages/CreateCustomer";

function App() {

    const { user } = useContext(AuthContext);

    const ProtectedRoute = ({ children }) => {
        if (!user) {
            return <Login title="Login to Create" />;
        } else {
            return children;
        }
    };

    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={
                    <ProtectedRoute>
                        <Customers />
                    </ProtectedRoute>
                } />
                <Route path="/login"
                    element={<Login />} />
                <Route path="/register"
                    element={<Register />} />
                <Route path="/create"
                    element={
                        <ProtectedRoute><CreateCustomer />
                        </ProtectedRoute>
                    } />
            </Routes>
        </BrowserRouter>
    );
}

export default App;
JavaScript
// src/authContext.js

import { createContext, useReducer, useEffect } from "react"

const INITIAL_STATE = {
    user: JSON.parse(localStorage.getItem("user")) || null,
    loading: false,
    error: null,
};


export const AuthContext = createContext(INITIAL_STATE)

const AuthReducer = (state, action) => {
    switch (action.type) {
        case "LOGIN_START":
            return {
                user: null,
                loading: true,
                error: null
            };

        case "LOGIN_SUCCESS":
            return {
                user: action.payload,
                loading: false,
                error: null
            };

        case "LOGIN_FAILURE":
            return {
                user: null,
                loading: false,
                error: action.payload
            };

        case "LOGOUT":
            return {
                user: null,
                loading: false,
                error: null
            };
        default:
            return state;
    }
}

export const AuthContextProvider = ({ children }) => {
    const [state, dispatch] =
        useReducer(AuthReducer, INITIAL_STATE)

    useEffect(() => {
        localStorage.setItem("user", JSON.stringify(state.user))
    }, [state.user])


    return (
        <AuthContext.Provider
            value={{
                user: state.user,
                loading: state.loading,
                error: state.error,
                dispatch
            }}>
            {children}
        </AuthContext.Provider>
    )
}
JavaScript
// src/useFetch.js

import { useEffect, useState } from "react";
import axios from "axios";

const useFetch = (url) => {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);

    useEffect(() => {
        const fetchData = async () => {
            setLoading(true);
            try {

                const res = await axios.get(url)

                setData(res.data);
            } catch (err) {
                setError(err);
            }
            setLoading(false);
        };
        fetchData();
    }, [url]);

    const reFetch = async () => {
        setLoading(true);
        try {
            const res = await axios.get(url)

            setData(res.data);
        } catch (err) {
            setError(err);
        }
        setLoading(false);
    };

    return { data, loading, error, reFetch };
};

export default useFetch;
JavaScript
// src/pages/Login.jsx

import React from "react";
import Navbar from "../components/Navbar";
import "../styles/login.css";
import axios from "axios";
import { useContext, useState } from "react";
import { useNavigate, Link } from "react-router-dom";
import { AuthContext } from "../authContext";

function Login() {
    const [credentials, setCredentials] = useState({
        username: undefined,
        password: undefined,
    });

    const { dispatch } = useContext(AuthContext);
    const navigate = useNavigate();

    const handleChange = (e) => {
        setCredentials(
            (prev) => ({ ...prev, [e.target.id]: e.target.value }));
    };

    const handleClick = async (e) => {
        e.preventDefault();
        dispatch({ type: "LOGIN_START" });
        try {
            const res =
                await axios.post(
                    "http://localhost:7700/api/users/login",
                    credentials);
            dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
            navigate('/');
        } catch (err) {
            if (err.response && err.response.data) {
                // If error response and data exist, 
                // dispatch LOGIN_FAILURE with error message
                dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
            } else {
                // If no error response or data, dispatch generic error message
                dispatch({
                    type: "LOGIN_FAILURE",
                    payload: "An error occurred while logging in"
                });
            }
        }
    };

    return (
        <div className="login">
            <Navbar />
            <div className="loginCard">
                <div className="center">
                    <h1>Welcome Back!</h1>
                    <form>
                        <div className="txt_field">
                            <input
                                type="text"
                                placeholder="username"
                                id="username"
                                onChange={handleChange}
                                className="lInput"/>
                        </div>
                        <div className="txt_field">
                            <input
                                type="password"
                                placeholder="password"
                                id="password"
                                onChange={handleChange}
                                className="lInput"/>
                        </div>
                        <div className="login_button">
                            <button className="button"
                                onClick={handleClick}>
                                Login
                            </button>
                        </div>
                        <div className="signup_link">
                            <p>
                                Not registered?
                                <Link to="/register">
                                    Register
                                </Link>
                            </p>
                        </div>
                    </form>

                </div>
            </div>
        </div>
    );
}

export default Login;
JavaScript
// src/pages/Register.jsx

import React from "react";
import Navbar from "../components/Navbar";
import "../styles/register.css";
import { Link } from "react-router-dom";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";

function Register() {
    const navigate = useNavigate();

    const [info, setInfo] = useState({});

    const handleChange = (e) => {
        setInfo(
            (prev) => ({ ...prev, [e.target.id]: e.target.value }));
    };

    const handleClick = async (e) => {
        e.preventDefault();

        try {
            await axios.post(
                "/users/register",
                info, { withcredentials: false })

            navigate("/login");
        } catch (err) {
            console.log(err)
        }

    };
    return (
        <div className="register">
            <Navbar />
            <div className="registerCard">
                <div className="center">
                    <h1>Join Us</h1>
                    <form>
                        <div className="formInput">
                            <div className="txt_field">
                                <input
                                    type="text"
                                    placeholder="username"
                                    name="username"
                                    onChange={handleChange}
                                    id="username"
                                    required />
                            </div>
                            <div className="txt_field">
                                <input
                                    type="email"
                                    placeholder="email"
                                    name="email"
                                    onChange={handleChange}
                                    id="email"
                                    required />
                            </div>
                            <div className="txt_field">
                                <input
                                    type="password"
                                    placeholder="password"
                                    name="password"
                                    onChange={handleChange}
                                    id="password"
                                    required />
                            </div>
                        </div>
                        <div className="login_button">
                            <button className="button" 
                                    onClick={handleClick}>
                                Register
                            </button>
                        </div>
                        <div className="signup_link">
                            <p>
                                Already Registered? 
                                <Link to="/login">Login</Link>
                            </p>
                        </div>
                    </form>
                </div>
            </div>

        </div>
    );
}

export default Register;
JavaScript
// src/pages/Customers.jsx

import React, { useContext } from 'react'
import useFetch from '../useFetch'
import { AuthContext } from '../authContext'
import CustCard from '../components/CustCard'
import Navbar from '../components/Navbar'
import "../styles/customers.css"

const Customers = ({ type }) => {

    const { user } = useContext(AuthContext)

    // Call useFetch unconditionally
    const { data } = useFetch(`/customers/${user._id}`)

    return (
        <div>
            <Navbar />
            <div className="cust-container">
                {data ? (
                    data?.map((item, index) => (
                        <CustCard key={index}
                            props={{ ...item, type }} />
                    ))
                ) : (
                    "No Customers Yet"
                )}
            </div>
        </div>
    )
}

export default Customers
JavaScript
// src/pages/CreateCustomer.jsx

import React, { useContext, useState } from "react";
import Navbar from '../components/Navbar'
import axios from "axios";
import { AuthContext } from "../authContext";
import "../styles/createCustomer.css"
import { useNavigate } from "react-router-dom";

const CreateCustomer = () => {

    const [info, setInfo] = useState({});
    const { user } = useContext(AuthContext);
    const navigate = useNavigate();

    const handleChange = (e) => {
        setInfo(
            (prev) => ({
                ...prev, [e.target.id]: e.target.value
            }
            ));
    };

    const handleClick = async (e) => {
        e.preventDefault();

        const newpost = {
            ...info,
            company: user._id,
        }

        try {
            await axios.post(
                "http://localhost:7700/api/customers",
                newpost)
            navigate('/')
        } catch (err) {
            console.log(err);
        }

    };

    return (
        <div className="createCustContainer">
            <Navbar />
            <div className="cpContainer">
                <div className="formContainer">
                    <div className="inputContainer">
                        <div className="input">
                            <label htmlFor="title">Name</label>
                            <input
                                onChange={handleChange}
                                type="text"
                                id="name"
                                placeholder="Enter Name"
                            />
                        </div>

                        <div className="input">
                            <label htmlFor="location">Service</label>
                            <input
                                onChange={handleChange}
                                type="text"
                                id="service"
                                placeholder="Enter service type"
                            />
                        </div>

                        <div className="input">
                            <label htmlFor="location">Email</label>
                            <input
                                onChange={handleChange}
                                type="email"
                                id="email"
                                placeholder="Enter email"
                            />
                        </div>

                        <div className="input">
                            <label htmlFor="price">Phone Number</label>
                            <input
                                onChange={handleChange}
                                type="text"
                                id="phone"
                                placeholder="Enter price range"
                            />
                        </div>

                        <div className="input">
                            <label>Choose Status</label>
                            <select id="status" onChange={handleChange}>
                                <option key={0} value="none">-</option>
                                <option key={1} value="not started">
                                    Not started
                                </option>
                                <option key={2} value="ongoing">
                                    Ongoing
                                </option>
                                <option key={3} value="completed">
                                    Completed
                                </option>
                            </select>
                        </div>

                        <button className="button"
                            onClick={handleClick} type="submit">
                            Add Customer
                        </button>
                    </div>
                </div>

            </div>
        </div>
    )
}

export default CreateCustomer
JavaScript
// src/customers/Navbar.jsx

import '../styles/navbar.css'
import { useContext } from 'react';
import { faBars } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate } from "react-router-dom"
import { AuthContext } from "../authContext"


const Navbar = () => {

    const navigate = useNavigate()

    const { user, dispatch } = useContext(AuthContext)
    const handleClick = async (e) => {
        e.preventDefault();
        dispatch({ type: "LOGOUT" });
        navigate("/")
    }
    return (
        <div className='navContainer'>
            <Link to="/">
                <p className='navLogo'>CRM Portal</p>
            </Link>

            <input type="checkbox" id='menu-bar' />
            <label htmlFor="menu-bar">
                <FontAwesomeIcon
                    icon={faBars}
                    className="icon" />
            </label>
            <nav className='navbar'>
                <ul>
                    <Link to="/create">
                        <li><p>Add Customers</p></li>
                    </Link>
                    <Link to="/">
                        <li><p>All Customers</p></li>
                    </Link>
                    {user ? (<>

                        <Link to={`/user/${user._id}`}>
                            <li onClick={handleClick}
                                style={{ cursor: "pointer" }}>
                                <p>Logout</p>
                            </li>
                            <li id="usernamename"><p>{user.username}</p></li>
                        </Link>
                    </>
                    ) :
                        (
                            <>
                                <Link to="/register">
                                    <li><p>Register</p></li>
                                </Link>
                                <Link to="/login">
                                    <li><p>Login</p></li>
                                </Link>
                            </>
                        )}
                </ul>
            </nav>
        </div>
    )
}

export default Navbar
JavaScript
// src/pages/CustCard.jsx

import React from 'react'
import "../styles/custCard.css"
import { faTrash } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import axios from 'axios';

const CustCard = ({ props }) => {

    const handleClick = async () => {
        try {
            await axios.delete(
                `http://localhost:7700/api/customers/${props._id}`,
                { withCredentials: false })
            window.location.reload();
        }
        catch (err) {
            console.log(err)
        }
    }

    const getStatusColor = (status) => {
        switch (status) {
            case 'not started':
                return 'grey';
            case 'ongoing':
                return 'orange';
            case 'completed':
                return 'green';
            default:
                return 'purple';
        }
    };

    return (
        <div className='cust-card'>
            <div className="details">
                <div className="cust-name">
                    <h1>{props.name}</h1>
                    <div className="status"
                         style={{ 
                             backgroundColor: getStatusColor(props.status) 
                            }}>
                        {props.status}
                    </div>
                </div>
                <div className='cust-details'>
                    <p>Service: </p> <span>{props.service}</span>
                    <p>Email: </p> <span>{props.email}</span>
                    <p>Phone Number: </p> <span>{props.phone}</span>
                </div>
            </div>
            <div className="icon">
                <FontAwesomeIcon
                    icon={faTrash}
                    onClick={handleClick} />
            </div>
        </div>
    )
}

export default CustCard

Step 4: To start the client, run the following commands in the terminal

npm start

Output:

Customer Relationship Management using Node and express

Final preview of the website




Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads