├── client
├── src
│ ├── App.css
│ ├── setupTests.js
│ ├── App.test.js
│ ├── components
│ │ ├── PublicRoute.js
│ │ ├── Spinner.js
│ │ ├── DoctorList.js
│ │ ├── ProtectedRoute.js
│ │ └── Layout.js
│ ├── redux
│ │ ├── store.js
│ │ └── features
│ │ │ ├── userSlice.js
│ │ │ └── alertSlice.js
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── Data
│ │ └── data.js
│ ├── pages
│ │ ├── HomePage.js
│ │ ├── Appointments.js
│ │ ├── admin
│ │ │ ├── Users.js
│ │ │ └── Doctors.js
│ │ ├── Login.js
│ │ ├── doctor
│ │ │ ├── DoctorAppointments.js
│ │ │ └── Profile.js
│ │ ├── Register.js
│ │ ├── BookingPage.js
│ │ ├── NotificationPage.js
│ │ └── ApplyDoctor.js
│ ├── index.css
│ ├── logo.svg
│ ├── styles
│ │ └── LayoutStyles.css
│ └── App.js
├── build
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── asset-manifest.json
│ ├── manifest.json
│ ├── index.html
│ └── static
│ │ ├── js
│ │ └── main.63fc4e3b.js.LICENSE.txt
│ │ └── css
│ │ ├── main.9cc7b55e.css
│ │ └── main.9cc7b55e.css.map
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── .gitignore
└── package.json
├── .gitignore
├── Images
├── Doctor Homepage.png
├── Homepage for Admin.png
├── Homepage from User.png
├── Manage Doctor Profile.png
├── Users List from Admin.png
├── User can apply for doctor.png
├── After Read Notification Page.png
├── Booking Appointment as User.png
├── Doctor List from admin page.png
├── Appointment Lists from User Profile.png
├── Appointment Lists from Doctor Profile.png
└── Notification Page for New Notifications.png
├── desktop.ini
├── vercel.json
├── package.json
├── config
└── connectDb.js
├── middlewares
└── authMiddleware.js
├── routes
├── adminRoute.js
├── doctorRoute.js
└── userRoute.js
├── index.js
├── models
├── appointmentModel.js
├── doctorModel.js
└── userModel.js
├── controllers
├── adminController.js
├── doctorController.js
└── userController.js
└── README.md
/client/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
--------------------------------------------------------------------------------
/client/build/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/build/favicon.ico
--------------------------------------------------------------------------------
/client/build/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/build/logo192.png
--------------------------------------------------------------------------------
/client/build/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/build/logo512.png
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/client/public/logo512.png
--------------------------------------------------------------------------------
/Images/Doctor Homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Doctor Homepage.png
--------------------------------------------------------------------------------
/Images/Homepage for Admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Homepage for Admin.png
--------------------------------------------------------------------------------
/Images/Homepage from User.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Homepage from User.png
--------------------------------------------------------------------------------
/Images/Manage Doctor Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Manage Doctor Profile.png
--------------------------------------------------------------------------------
/Images/Users List from Admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Users List from Admin.png
--------------------------------------------------------------------------------
/Images/User can apply for doctor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/User can apply for doctor.png
--------------------------------------------------------------------------------
/Images/After Read Notification Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/After Read Notification Page.png
--------------------------------------------------------------------------------
/Images/Booking Appointment as User.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Booking Appointment as User.png
--------------------------------------------------------------------------------
/Images/Doctor List from admin page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Doctor List from admin page.png
--------------------------------------------------------------------------------
/desktop.ini:
--------------------------------------------------------------------------------
1 | [.ShellClassInfo]
2 | IconResource=C:\Windows\System32\SHELL32.dll,23
3 | [ViewState]
4 | Mode=
5 | Vid=
6 | FolderType=Generic
7 |
--------------------------------------------------------------------------------
/Images/Appointment Lists from User Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Appointment Lists from User Profile.png
--------------------------------------------------------------------------------
/Images/Appointment Lists from Doctor Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Appointment Lists from Doctor Profile.png
--------------------------------------------------------------------------------
/Images/Notification Page for New Notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OviSarkar62/AppointDoc/HEAD/Images/Notification Page for New Notifications.png
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "builds": [
3 | {
4 | "src": "./index.js",
5 | "use": "@vercel/node"
6 | }
7 | ],
8 | "routes": [
9 | {
10 | "src": "/.*",
11 | "dest": "index.js"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/src/components/PublicRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 |
4 | export default function PublicRoute({ children }) {
5 | if (localStorage.getItem("token")) {
6 | return ;
7 | } else {
8 | return children;
9 | }
10 | }
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 |
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { alertSlice } from "./features/alertSlice";
3 | import { userSlice } from "./features/userSlice";
4 |
5 | export default configureStore({
6 | reducer: {
7 | alerts: alertSlice.reducer,
8 | user: userSlice.reducer,
9 | },
10 | });
--------------------------------------------------------------------------------
/client/src/redux/features/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const userSlice = createSlice({
4 | name: "user",
5 | initialState: {
6 | user: null,
7 | },
8 | reducers: {
9 | setUser: (state, action) => {
10 | state.user = action.payload;
11 | },
12 | },
13 | });
14 |
15 | export const { setUser } = userSlice.actions;
--------------------------------------------------------------------------------
/client/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Spinners from "react-bootstrap/Spinner";
3 |
4 | const Spinner = () => {
5 | return (
6 | <>
7 |
8 |
9 | Loading...
10 |
11 | >
12 | );
13 | };
14 |
15 | export default Spinner;
16 |
--------------------------------------------------------------------------------
/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/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.9cc7b55e.css",
4 | "main.js": "/static/js/main.63fc4e3b.js",
5 | "index.html": "/index.html",
6 | "main.9cc7b55e.css.map": "/static/css/main.9cc7b55e.css.map",
7 | "main.63fc4e3b.js.map": "/static/js/main.63fc4e3b.js.map"
8 | },
9 | "entrypoints": [
10 | "static/css/main.9cc7b55e.css",
11 | "static/js/main.63fc4e3b.js"
12 | ]
13 | }
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import "antd/dist/reset.css";
2 | import React from "react";
3 | import ReactDOM from "react-dom/client";
4 | import { Provider } from "react-redux";
5 | import App from "./App";
6 | import "./index.css";
7 | import store from "./redux/store";
8 |
9 |
10 | const root = ReactDOM.createRoot(document.getElementById("root"));
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 |
--------------------------------------------------------------------------------
/client/build/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appointdoctor",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "bcrypt": "^5.1.0",
14 | "bcryptjs": "^2.4.3",
15 | "colors": "^1.4.0",
16 | "dotenv": "^16.0.3",
17 | "express": "^4.18.2",
18 | "joi": "^17.9.2",
19 | "jsonwebtoken": "^9.0.0",
20 | "moment": "^2.29.4",
21 | "mongoose": "^7.1.0",
22 | "morgan": "^1.10.0",
23 | "nodemon": "^2.0.22",
24 | "zxcvbn": "^4.4.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/config/connectDb.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const colors = require("colors");
3 |
4 | const connectDb = async () => {
5 | try {
6 | if (!process.env.DB_URL) {
7 | throw new Error("DB_URL environment variable not set");
8 | }
9 |
10 | await mongoose.connect(process.env.DB_URL, {
11 | useNewUrlParser: true,
12 | useUnifiedTopology: true,
13 | });
14 |
15 | console.log(`Server running on ${mongoose.connection.host}`.bgCyan.white);
16 | } catch (error) {
17 | console.error(`Error connecting to database: ${error}`.bgRed);
18 | process.exit(1);
19 | }
20 | };
21 |
22 | module.exports = connectDb;
--------------------------------------------------------------------------------
/middlewares/authMiddleware.js:
--------------------------------------------------------------------------------
1 | const JWT = require("jsonwebtoken");
2 |
3 | module.exports = async (req, res, next) => {
4 | try {
5 | const token = req.headers["authorization"].split(" ")[1];
6 | JWT.verify(token, process.env.JWT_SECRET, (err, decode) => {
7 | if (err) {
8 | return res.status(200).send({
9 | message: "Auth Fialed",
10 | success: false,
11 | });
12 | } else {
13 | req.body.userId = decode.id;
14 | next();
15 | }
16 | });
17 | } catch (error) {
18 | console.log(error);
19 | res.status(401).send({
20 | message: "Auth Failed",
21 | success: false,
22 | });
23 | }
24 | };
--------------------------------------------------------------------------------
/routes/adminRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | getAllUsersController,
4 | getAllDoctorsController,
5 | changeAccountStatusController,
6 | } = require("../controllers/adminController");
7 | const authMiddleware = require("../middlewares/authMiddleware");
8 |
9 |
10 | const router = express.Router();
11 |
12 | //GET METHOD || USERS
13 | router.get("/getAllUsers", authMiddleware, getAllUsersController);
14 |
15 | //GET METHOD || DOCTORS
16 | router.get("/getAllDoctors", authMiddleware, getAllDoctorsController);
17 |
18 | //POST ACCOUNT STATUS
19 | router.post("/changeAccountStatus", authMiddleware, changeAccountStatusController);
20 |
21 |
22 | module.exports = router;
23 |
--------------------------------------------------------------------------------
/client/src/Data/data.js:
--------------------------------------------------------------------------------
1 | export const userMenu = [
2 | {
3 | name: "Home",
4 | path: "/",
5 | icon: "fa-solid fa-house",
6 | },
7 | {
8 | name: "Appointments",
9 | path: "/appointments",
10 | icon: "fa-solid fa-list",
11 | },
12 | {
13 | name: "Apply Doctor",
14 | path: "/apply-doctor",
15 | icon: "fa-solid fa-user-doctor",
16 | }
17 | ];
18 |
19 | // admin menu
20 | export const adminMenu = [
21 | {
22 | name: "Home",
23 | path: "/",
24 | icon: "fa-solid fa-house",
25 | },
26 |
27 | {
28 | name: "Doctors",
29 | path: "/admin/doctors",
30 | icon: "fa-solid fa-user-doctor",
31 | },
32 | {
33 | name: "Users",
34 | path: "/admin/users",
35 | icon: "fa-solid fa-user",
36 | }
37 | ];
38 |
39 |
--------------------------------------------------------------------------------
/client/src/redux/features/alertSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const alertSlice = createSlice({
4 | name: "alerts",
5 | initialState: {
6 | loading: false,
7 | user: {
8 | notification: [],
9 | seennotification: [],
10 | },
11 | },
12 | reducers: {
13 | showLoading: (state) => {
14 | state.loading = true;
15 | },
16 | hideLoading: (state) => {
17 | state.loading = false;
18 | },
19 | setUserNotification: (state, action) => {
20 | state.user.notification = action.payload;
21 | },
22 | setSeenNotification: (state, action) => {
23 | state.user.seennotification = action.payload;
24 | },
25 | },
26 | });
27 |
28 | export const {
29 | showLoading,
30 | hideLoading,
31 | setUserNotification,
32 | setSeenNotification,
33 | } = alertSlice.actions;
34 |
35 |
36 |
--------------------------------------------------------------------------------
/routes/doctorRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | getDoctorInfoController,
4 | updateProfileController,
5 | getDoctorByIdController,
6 | doctorAppointmentsController,
7 | updateStatusController,
8 | } = require("../controllers/doctorController");
9 | const authMiddleware = require("../middlewares/authMiddleware");
10 | const router = express.Router();
11 |
12 | //POST SINGLE DOC INFO
13 | router.post("/getDoctorInfo", authMiddleware, getDoctorInfoController);
14 |
15 | //POST UPDATE PROFILE
16 | router.post("/updateProfile", authMiddleware, updateProfileController);
17 |
18 | //POST GET SINGLE DOC INFO
19 | router.post("/getDoctorById", authMiddleware, getDoctorByIdController);
20 |
21 | //GET Appointments
22 | router.get("/doctor-appointments", authMiddleware, doctorAppointmentsController);
23 |
24 | //POST Update Status
25 | router.post("/update-status", authMiddleware, updateStatusController);
26 |
27 | module.exports = router;
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const colors = require("colors");
3 | const moragan = require("morgan");
4 | const dotenv = require("dotenv");
5 | const userRoutes = require('./routes/userRoute');
6 | const adminRoutes = require("./routes/adminRoute");
7 | const doctorRoutes = require("./routes/doctorRoute")
8 | const connectDb = require("./config/connectDb");
9 | const path = require("path");
10 |
11 | //dotenv conig
12 | dotenv.config();
13 | connectDb();
14 |
15 | //rest obejct
16 | const app = express();
17 |
18 | //middlewares
19 | app.use(express.json());
20 | app.use(moragan("dev"));
21 |
22 | //routes
23 | app.use("/api/user", userRoutes);
24 | app.use("/api/admin", adminRoutes);
25 | app.use("/api/doctor", doctorRoutes);
26 |
27 | //static files
28 | app.use(express.static(path.join(__dirname, "./client/build")));
29 |
30 | app.get("*", function (req, res) {
31 | res.sendFile(path.join(__dirname, "./client/build/index.html"));
32 | });
33 |
34 | //port
35 | const PORT = process.env.PORT || 4001;
36 | //listen port
37 | app.listen(PORT, () => {
38 | console.log(`Server running on http://localhost:${PORT}`);
39 | });
--------------------------------------------------------------------------------
/client/src/pages/HomePage.js:
--------------------------------------------------------------------------------
1 | import { Row } from "antd";
2 | import axios from "axios";
3 | import React, { useEffect, useState } from "react";
4 | import DoctorList from "../components/DoctorList";
5 | import Layout from "./../components/Layout";
6 |
7 | const HomePage = () => {
8 | const [doctors, setDoctors] = useState([]);
9 | // login user data
10 | const getUserData = async () => {
11 | try {
12 | const res = await axios.get(
13 | "/api/user/getAllDoctors",
14 |
15 | {
16 | headers: {
17 | Authorization: "Bearer " + localStorage.getItem("token"),
18 | },
19 | }
20 | );
21 | if (res.data.success) {
22 | setDoctors(res.data.data);
23 | }
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | };
28 |
29 | useEffect(() => {
30 | getUserData();
31 | }, []);
32 | return (
33 |
34 | Home Page
35 |
36 |
37 | {doctors && doctors.map((doctor) => )}
38 |
39 |
40 | );
41 | };
42 |
43 | export default HomePage;
--------------------------------------------------------------------------------
/client/src/components/DoctorList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | const DoctorList = ({ doctor }) => {
5 | const navigate = useNavigate();
6 | return (
7 | <>
8 | navigate(`/doctor/book-appointment/${doctor._id}`)}
12 | >
13 |
17 | Dr. {doctor.firstName} {doctor.lastName}
18 |
19 |
20 |
21 |
22 | Specialization: {doctor.specialization}
23 |
24 |
25 | Experience: {doctor.experience}
26 |
27 |
28 | Fees Per Consultation: {doctor.feesPerConsultation}
29 |
30 |
31 | Timings: {doctor.starttime} - {doctor.endtime}
32 |
33 |
34 |
35 | >
36 | );
37 | };
38 |
39 | export default DoctorList;
40 |
--------------------------------------------------------------------------------
/client/build/index.html:
--------------------------------------------------------------------------------
1 | Appoint Doctor
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "proxy": "http://localhost:4000",
3 | "name": "client",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "@testing-library/jest-dom": "^5.16.5",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "antd": "^5.4.6",
12 | "axios": "^1.4.0",
13 | "bootstrap": "^5.2.3",
14 | "moment": "^2.29.4",
15 | "react": "^18.2.0",
16 | "react-bootstrap": "^2.7.4",
17 | "react-dom": "^18.2.0",
18 | "react-redux": "^8.0.5",
19 | "react-router-dom": "^6.11.0",
20 | "react-scripts": "5.0.1",
21 | "react-spinners": "^0.13.8",
22 | "web-vitals": "^2.1.4"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | position: absolute;
3 | top: 50%;
4 | left: 50%;
5 | transform: translate(-50%, -50%);
6 | z-index: 100;
7 | }
8 |
9 | .spinner-border {
10 | height: 50px;
11 | width: 50px;
12 | }
13 |
14 | /*register page*/
15 |
16 | .register-page {
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | height: 90vh;
21 | }
22 |
23 | .register-page form {
24 | width: 400px;
25 | background-color: #fff;
26 | padding: 40px;
27 | border-radius: 10px;
28 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
29 | }
30 | .register-page form .d-flex {
31 | display: flex;
32 | justify-content: space-between;
33 | align-items: center;
34 | margin-top: 20px;
35 | }
36 |
37 | .register-page form .d-flex a {
38 | color: #007bff;
39 | text-decoration: none;
40 | }
41 |
42 | .register-page h1 {
43 | text-align: center;
44 | margin-bottom: 20px;
45 | }
46 |
47 | .register-page .ant-form-item {
48 | margin-bottom: 20px;
49 | }
50 |
51 | .register-page .ant-form-item-label {
52 | font-weight: bold;
53 | }
54 |
55 | .register-page .ant-form-item-explain {
56 | color: #ff4d4f;
57 | }
58 |
59 | .register-page .ant-form-item-has-error .ant-input {
60 | border-color: #ff4d4f;
61 | }
62 |
63 | .register-page .ant-form-item-has-error .ant-input:focus {
64 | box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);
65 | }
66 |
67 | .register-page .btn-primary {
68 | width: 100%;
69 | }
70 |
--------------------------------------------------------------------------------
/client/src/pages/Appointments.js:
--------------------------------------------------------------------------------
1 | import { Table } from "antd";
2 | import axios from "axios";
3 | import moment from "moment";
4 | import React, { useEffect, useState } from "react";
5 | import Layout from "./../components/Layout";
6 |
7 |
8 | const Appointments = () => {
9 | const [appointments, setAppointments] = useState([]);
10 |
11 | const getAppointments = async () => {
12 | try {
13 | const res = await axios.get("/api/user/user-appointments", {
14 | headers: {
15 | Authorization: `Bearer ${localStorage.getItem("token")}`,
16 | },
17 | });
18 | if (res.data.success) {
19 | setAppointments(res.data.data);
20 | }
21 | } catch (error) {
22 | console.log(error);
23 | }
24 | };
25 |
26 | useEffect(() => {
27 | getAppointments();
28 | }, []);
29 |
30 | const columns = [
31 | {
32 | title: "ID",
33 | dataIndex: "_id",
34 | },
35 | {
36 | title: "Date & Time",
37 | dataIndex: "date",
38 | render: (text, record) => (
39 |
40 | {moment(record.date).format("DD-MM-YYYY")}
41 | {moment(record.time).format("HH:mm")}
42 |
43 | ),
44 | },
45 | {
46 | title: "Status",
47 | dataIndex: "status",
48 | },
49 | ];
50 |
51 | return (
52 |
53 | Appointments Lists
54 |
55 |
56 | );
57 | };
58 |
59 | export default Appointments;
60 |
--------------------------------------------------------------------------------
/models/appointmentModel.js:
--------------------------------------------------------------------------------
1 | const Joi = require('joi');
2 | const mongoose = require('mongoose');
3 |
4 | const appointmentSchema = new mongoose.Schema(
5 | {
6 | userId: {
7 | type: String,
8 | required: true,
9 | },
10 | doctorId: {
11 | type: String,
12 | required: true,
13 | },
14 | doctorInfo: {
15 | type: String,
16 | required: true,
17 | },
18 | userInfo: {
19 | type: String,
20 | required: true,
21 | },
22 | date: {
23 | type: Date, // changed type to Date
24 | required: true,
25 | },
26 | status: {
27 | type: String,
28 | required: true,
29 | default: 'pending',
30 | },
31 | time: {
32 | type: String,
33 | required: true,
34 | },
35 | },
36 | { timestamps: true }
37 | );
38 |
39 |
40 | // Define a Joi schema for appointment validation
41 | const appointmentJoiSchema = Joi.object({
42 | userId: Joi.string().required(),
43 | doctorId: Joi.string().required(),
44 | doctorInfo: Joi.string().required(),
45 | userInfo: Joi.string().required(),
46 | date: Joi.date().required(),
47 | status: Joi.string().required().default('pending'),
48 | time: Joi.string().required(),
49 | }).options({ stripUnknown: true });
50 |
51 |
52 | const AppointmentModel = mongoose.model('appointments', appointmentSchema);
53 |
54 | // Add the Joi validation to the Mongoose schema
55 | AppointmentModel.validateAppointment = async function () {
56 | return appointmentJoiSchema.validateAsync(this.toObject());
57 | };
58 |
59 | module.exports = AppointmentModel;
60 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
20 |
27 | Appoint Doctor
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/client/src/pages/admin/Users.js:
--------------------------------------------------------------------------------
1 | import { Table } from "antd";
2 | import axios from "axios";
3 | import React, { useEffect, useState } from "react";
4 | import Layout from "./../../components/Layout";
5 | const Users = () => {
6 | const [users, setUsers] = useState([]);
7 |
8 | //getUsers
9 | const getUsers = async () => {
10 | try {
11 | const res = await axios.get("/api/admin/getAllUsers", {
12 | headers: {
13 | Authorization: `Bearer ${localStorage.getItem("token")}`,
14 | },
15 | });
16 | if (res.data.success) {
17 | setUsers(res.data.data);
18 | }
19 | } catch (error) {
20 | console.log(error);
21 | }
22 | };
23 |
24 | useEffect(() => {
25 | getUsers();
26 | }, []);
27 |
28 | // antD table col
29 | const columns = [
30 | {
31 | title: "Name",
32 | dataIndex: "name",
33 | },
34 | {
35 | title: "Email",
36 | dataIndex: "email",
37 | },
38 | {
39 | title: "Doctor",
40 | dataIndex: "isDoctor",
41 | render: (text, record) => {record.isDoctor ? "Yes" : "No"},
42 | },
43 | {
44 | title: "Actions",
45 | dataIndex: "actions",
46 | render: (text, record) => (
47 |
48 |
49 |
50 | ),
51 | },
52 | ];
53 |
54 | return (
55 |
56 | Users List
57 |
58 |
59 | );
60 | };
61 |
62 | export default Users;
--------------------------------------------------------------------------------
/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | loginController,
4 | registerController,
5 | authController,
6 | applyDoctorController,
7 | getAllNotificationController,
8 | deleteAllNotificationController,
9 | getAllDocotrsController,
10 | bookAppointmentController,
11 | bookingAvailabilityController,
12 | userAppointmentsController,
13 | } = require("../controllers/userController");
14 | const authMiddleware = require("../middlewares/authMiddleware");
15 |
16 | //router object
17 | const router = express.Router();
18 |
19 | //routes
20 | // POST || LOGIN USER
21 | router.post("/login", loginController);
22 |
23 | //POST || REGISTER USER
24 | router.post("/register", registerController);
25 |
26 | //Auth || POST
27 | router.post("/getUserData", authMiddleware, authController);
28 |
29 | //Apply Doctor || POST
30 | router.post("/apply-doctor", authMiddleware, applyDoctorController);
31 |
32 | //Notifiaction Doctor || POST
33 | router.post("/get-all-notification", authMiddleware, getAllNotificationController);
34 |
35 | //Notifiaction Doctor || POST
36 | router.post("/delete-all-notification", authMiddleware, deleteAllNotificationController);
37 |
38 | //GET ALL DOC
39 | router.get("/getAllDoctors", authMiddleware, getAllDocotrsController);
40 |
41 | //BOOK APPOINTMENT
42 | router.post("/book-appointment", authMiddleware, bookAppointmentController);
43 |
44 | //Booking Avliability
45 | router.post("/booking-availbility", authMiddleware, bookingAvailabilityController);
46 |
47 | //Appointments List
48 | router.get("/user-appointments", authMiddleware, userAppointmentsController);
49 |
50 | module.exports = router;
--------------------------------------------------------------------------------
/client/src/components/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { Navigate } from "react-router-dom";
5 | import { hideLoading, showLoading } from "../redux/features/alertSlice";
6 | import { setUser } from "../redux/features/userSlice";
7 |
8 |
9 | export default function ProtectedRoute({ children }) {
10 | const dispatch = useDispatch();
11 | const { user } = useSelector((state) => state.user);
12 |
13 | useEffect(() => {
14 | const getUser = async () => {
15 | try {
16 | dispatch(showLoading());
17 | const {data} = await axios.post(
18 | "/api/user/getUserData",
19 | { token: localStorage.getItem("token") },
20 | {
21 | headers: {
22 | Authorization: `Bearer ${localStorage.getItem("token")}`,
23 | },
24 | }
25 | );
26 | dispatch(hideLoading());
27 | if (data.success) {
28 | dispatch(setUser(data.data));
29 | } else {
30 | localStorage.clear();
31 | ;
32 | }
33 | } catch (error) {
34 | localStorage.clear();
35 | dispatch(hideLoading());
36 | console.log(error);
37 | }
38 | };
39 |
40 | if (!user) {
41 | getUser();
42 | }
43 | }, [user, dispatch]);
44 |
45 | if (localStorage.getItem("token")) {
46 | return children;
47 | } else {
48 | return ;
49 | }
50 | }
51 |
52 |
53 |
--------------------------------------------------------------------------------
/client/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import { Form, Input, message } from "antd";
2 | import axios from "axios";
3 | import React from "react";
4 | import { useDispatch } from "react-redux";
5 | import { Link, useNavigate } from "react-router-dom";
6 | import { hideLoading, showLoading } from "../redux/features/alertSlice";
7 |
8 | const Login = () => {
9 | const dispatch = useDispatch();
10 | const navigate = useNavigate();
11 |
12 | // Submit for Login
13 | const submitHandler = async (values) => {
14 | try {
15 | dispatch(showLoading());
16 | const { data } = await axios.post("/api/user/login", values);
17 | window.location.reload();
18 | dispatch(hideLoading());
19 | if (data.success) {
20 | localStorage.setItem("token", data.token);
21 | message.success("Login Successfully");
22 | navigate("/");
23 | } else {
24 | message.error(data.message);
25 | }
26 | } catch (error) {
27 | dispatch(hideLoading());
28 | message.error("Something went wrong");
29 | }
30 | };
31 |
32 | return (
33 | <>
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Not a User? Click Here to Register
45 |
46 |
47 |
48 |
49 | >
50 | );
51 | };
52 |
53 | export default Login;
--------------------------------------------------------------------------------
/controllers/adminController.js:
--------------------------------------------------------------------------------
1 | const doctorModel = require('../models/doctorModel');
2 | const userModel = require("../models/userModel");
3 |
4 |
5 | const getAllUsersController = async (req, res) => {
6 | try {
7 | const users = await userModel.find({});
8 | res.status(200).send({
9 | success: true,
10 | message: "Users Data List",
11 | data: users,
12 | });
13 | } catch (error) {
14 | console.log(error);
15 | res.status(500).send({
16 | success: false,
17 | message: "Error While Fetching Users",
18 | error,
19 | });
20 | }
21 | };
22 |
23 | //GET ALL DOC
24 | const getAllDoctorsController = async (req, res) => {
25 | try {
26 | const doctors = await doctorModel.find({});
27 | res.status(200).send({
28 | success: true,
29 | message: "Doctors Data list",
30 | data: doctors,
31 | });
32 | } catch (error) {
33 | console.log(error);
34 | res.status(500).send({
35 | success: false,
36 | message: "Error While Getting Doctors Data",
37 | error,
38 | });
39 | }
40 | };
41 |
42 |
43 | // doctor account status
44 | const changeAccountStatusController = async (req, res) => {
45 | try {
46 | const { doctorId, status } = req.body;
47 | const doctor = await doctorModel.findByIdAndUpdate(doctorId, { status });
48 | const user = await userModel.findOne({ _id: doctor.userId });
49 | const notification = user.notification;
50 | notification.push({
51 | type: "doctor-account-request-updated",
52 | message: `Your Doctor Account Request Has ${status} `,
53 | onClickPath: "/notification",
54 | });
55 | user.isDoctor = status === "approved" ? true : false;
56 | await user.save();
57 | res.status(201).send({
58 | success: true,
59 | message: "Account Status Updated",
60 | data: doctor,
61 | });
62 | } catch (error) {
63 | console.log(error);
64 | res.status(500).send({
65 | success: false,
66 | message: "Error In Account Status",
67 | error,
68 | });
69 | }
70 | };
71 |
72 |
73 | module.exports = { getAllDoctorsController, getAllUsersController, changeAccountStatusController };
--------------------------------------------------------------------------------
/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/pages/admin/Doctors.js:
--------------------------------------------------------------------------------
1 | import { Table, message } from "antd";
2 | import axios from "axios";
3 | import React, { useEffect, useState } from "react";
4 | import Layout from "./../../components/Layout";
5 |
6 | const Doctors = () => {
7 | const [doctors, setDoctors] = useState([]);
8 | //getUsers
9 | const getDoctors = async () => {
10 | try {
11 | const res = await axios.get("/api/admin/getAllDoctors", {
12 | headers: {
13 | Authorization: `Bearer ${localStorage.getItem("token")}`,
14 | },
15 | });
16 | if (res.data.success) {
17 | setDoctors(res.data.data);
18 | }
19 | } catch (error) {
20 | console.log(error);
21 | }
22 | };
23 |
24 | // handle account
25 | const handleAccountStatus = async (record, status) => {
26 | try {
27 | const res = await axios.post(
28 | "/api/admin/changeAccountStatus",
29 | { doctorId: record._id, userId: record.userId, status: status },
30 | {
31 | headers: {
32 | Authorization: `Bearer ${localStorage.getItem("token")}`,
33 | },
34 | }
35 | );
36 | if (res.data.success) {
37 | message.success(res.data.message);
38 | window.location.reload();
39 | }
40 | } catch (error) {
41 | message.error("Something Went Wrong");
42 | }
43 | };
44 |
45 | useEffect(() => {
46 | getDoctors();
47 | }, []);
48 |
49 | const columns = [
50 | {
51 | title: "Name",
52 | dataIndex: "name",
53 | render: (text, record) => (
54 |
55 | {record.firstName} {record.lastName}
56 |
57 | ),
58 | },
59 | {
60 | title: "Status",
61 | dataIndex: "status",
62 | },
63 | {
64 | title: "Phone",
65 | dataIndex: "phone",
66 | },
67 | {
68 | title: "Actions",
69 | dataIndex: "actions",
70 | render: (text, record) => (
71 |
72 | {record.status === "pending" ? (
73 |
79 | ) : (
80 |
81 | )}
82 |
83 | ),
84 | },
85 | ];
86 |
87 | return (
88 |
89 | All Doctors
90 |
91 |
92 | );
93 | };
94 |
95 | export default Doctors;
--------------------------------------------------------------------------------
/models/doctorModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const Joi = require('joi');
3 |
4 | const doctorSchema = new mongoose.Schema({
5 | userId: {
6 | type: String,
7 | },
8 | firstName: {
9 | type: String,
10 | required: [true, "First name is required"],
11 | minlength: [2, 'Your first name must be at least 2 characters'],
12 | maxlength: [50, 'Your first name cannot exceed 50 characters'],
13 | },
14 | lastName: {
15 | type: String,
16 | required: [true, "Last name is required"],
17 | minlength: [2, 'Your last name must be at least 2 characters'],
18 | maxlength: [50, 'Your last name cannot exceed 50 characters'],
19 | },
20 | phone: {
21 | type: String,
22 | required: [true, "Phone number is required"],
23 | },
24 | email: {
25 | type: String,
26 | required: [true, "Email is required"],
27 | unique: true,
28 | lowercase: true,
29 | },
30 | website: {
31 | type: String,
32 | },
33 | address: {
34 | type: String,
35 | required: [true, "Address is required"],
36 | },
37 | specialization: {
38 | type: String,
39 | required: [true, "Specialization is required"],
40 | },
41 | experience: {
42 | type: String,
43 | required: [true, "Experience is required"],
44 | },
45 | feesPerConsultation: {
46 | type: Number,
47 | required: [true, "Fee is required"],
48 | },
49 | status: {
50 | type: String,
51 | default: "pending",
52 | },
53 | starttime: {
54 | type: String,
55 | required: [true],
56 | },
57 | endtime: {
58 | type: String,
59 | required: [true],
60 | },
61 | createdAt: {
62 | type: Date,
63 | default: Date.now,
64 | },
65 | });
66 |
67 | const docSchema = Joi.object({
68 | userId: Joi.string(),
69 | firstName: Joi.string().required().min(2).max(50),
70 | lastName: Joi.string().required().min(2).max(50),
71 | phone: Joi.string().required(),
72 | email: Joi.string().required().email(),
73 | website: Joi.string(),
74 | address: Joi.string().required(),
75 | specialization: Joi.string().required(),
76 | experience: Joi.string().required(),
77 | feesPerConsultation: Joi.number().required(),
78 | status: Joi.string().default('pending'),
79 | starttime: Joi.string().required(),
80 | endtime: Joi.string().required(),
81 | createdAt: Joi.date().default(Date.now),
82 | });
83 |
84 | // Add the Joi validation to the Mongoose schema
85 | doctorSchema.validateDoctor = async function () {
86 | return docSchema.validateAsync(this.toObject());
87 | };
88 |
89 | const doctorModel = mongoose.model("doctors", doctorSchema);
90 | module.exports = doctorModel;
91 |
--------------------------------------------------------------------------------
/client/src/pages/doctor/DoctorAppointments.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Layout from "./../../components/Layout";
3 |
4 | import axios from "axios";
5 |
6 | import { message, Table } from "antd";
7 | import moment from "moment";
8 |
9 | const DoctorAppointments = () => {
10 | const [appointments, setAppointments] = useState([]);
11 |
12 | const getAppointments = async () => {
13 | try {
14 | const res = await axios.get("/api/doctor/doctor-appointments", {
15 | headers: {
16 | Authorization: `Bearer ${localStorage.getItem("token")}`,
17 | },
18 | });
19 | if (res.data.success) {
20 | setAppointments(res.data.data);
21 | }
22 | } catch (error) {
23 | console.log(error);
24 | }
25 | };
26 |
27 | useEffect(() => {
28 | getAppointments();
29 | }, []);
30 |
31 | const handleStatus = async (record, status) => {
32 | try {
33 | const res = await axios.post(
34 | "/api/doctor/update-status",
35 | { appointmentsId: record._id, status },
36 | {
37 | headers: {
38 | Authorization: `Bearer ${localStorage.getItem("token")}`,
39 | },
40 | }
41 | );
42 | if (res.data.success) {
43 | message.success(res.data.message);
44 | getAppointments();
45 | }
46 | } catch (error) {
47 | console.log(error);
48 | message.error("Something Went Wrong");
49 | }
50 | };
51 |
52 | const columns = [
53 | {
54 | title: "ID",
55 | dataIndex: "_id",
56 | },
57 | {
58 | title: "Date & Time",
59 | dataIndex: "date",
60 | render: (text, record) => (
61 |
62 | {moment(record.date).format("DD-MM-YYYY")}
63 | {moment(record.time).format("HH:mm")}
64 |
65 | ),
66 | },
67 | {
68 | title: "Status",
69 | dataIndex: "status",
70 | },
71 | {
72 | title: "Actions",
73 | dataIndex: "actions",
74 | render: (text, record) => (
75 |
76 | {record.status === "pending" && (
77 |
78 |
84 |
90 |
91 | )}
92 |
93 | ),
94 | },
95 | ];
96 | return (
97 |
98 | Appointments Lists
99 |
100 |
101 | );
102 | };
103 |
104 | export default DoctorAppointments;
--------------------------------------------------------------------------------
/client/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import { Form, Input, message } from "antd";
2 | import axios from "axios";
3 | import React from "react";
4 | import { useDispatch } from "react-redux";
5 | import { Link, useNavigate } from "react-router-dom";
6 | import { hideLoading, showLoading } from "../redux/features/alertSlice";
7 |
8 | const Register = () => {
9 | const navigate = useNavigate();
10 | const dispatch = useDispatch();
11 |
12 | // Submit for Register
13 | const submitHandler = async (values) => {
14 | try {
15 | dispatch(showLoading());
16 | const { data } = await axios.post("/api/user/register", values);
17 | dispatch(hideLoading());
18 | if (data.success) {
19 | message.success("Registration Successful");
20 | navigate("/login");
21 | } else {
22 | message.error(data.message);
23 | }
24 | } catch (error) {
25 | dispatch(hideLoading());
26 | message.error("Registration Failed");
27 | }
28 | };
29 |
30 | return (
31 | <>
32 |
33 |
44 |
45 |
46 |
54 |
55 |
56 |
\\/?])/,
66 | message:
67 | "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character",
68 | },
69 | ]}
70 | >
71 |
72 |
73 |
74 | Already Registered? Click Here to Login
75 |
76 |
77 |
78 |
79 | >
80 | );
81 | };
82 |
83 | export default Register;
--------------------------------------------------------------------------------
/client/src/styles/LayoutStyles.css:
--------------------------------------------------------------------------------
1 | /* Light mode styles */
2 | .main {
3 | padding: 20px;
4 | height: 100vh;
5 | background-color: #fff;
6 | color: #3d3d3d;
7 | }
8 |
9 | .layout {
10 | display: flex;
11 | }
12 |
13 | .sidebar {
14 | min-height: 760px;
15 | height: auto;
16 | width: 300px;
17 | border-radius: 8px;
18 | background-color: #f7f7f7;
19 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
20 | margin-right: 20px;
21 | color: #3d3d3d;
22 | font-size: 1.1rem;
23 | font-weight: 600;
24 | }
25 |
26 | .content {
27 | width: 100%;
28 | height: 100%;
29 | }
30 |
31 | .header {
32 | height: 60px;
33 | margin-bottom: 20px;
34 | border-radius: 8px;
35 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
36 | background-color: #fff;
37 | display: flex;
38 | align-items: center;
39 | padding: 0 20px;
40 | }
41 |
42 | .body {
43 | height: calc(100% - 80px);
44 | border-radius: 8px;
45 | margin-bottom: 20px;
46 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
47 | background-color: #fff;
48 | overflow-y: auto;
49 | padding: 20px;
50 | }
51 |
52 | .logo h6 {
53 | font-size: 1.5rem;
54 | text-align: center;
55 | margin: 20px 0px;
56 | color: #3d3d3d;
57 | font-weight: 700;
58 | letter-spacing: 2px;
59 | }
60 |
61 | .menu {
62 | margin-top: 50px;
63 | }
64 |
65 | .menu-item {
66 | margin-top: 25px;
67 | display: flex;
68 | align-items: center;
69 | padding: 10px 20px;
70 | border-radius: 8px;
71 | transition: all 0.3s ease;
72 | cursor: pointer;
73 | }
74 |
75 | .menu-item:hover {
76 | background-color: grey;
77 | }
78 |
79 | .menu-item a {
80 | color: #3d3d3d;
81 | text-decoration: none;
82 | margin-left: 20px;
83 | }
84 |
85 | .menu-item i {
86 | font-size: 1.2rem;
87 | margin-right: 20px;
88 | color: #3d3d3d;
89 | }
90 |
91 | .active {
92 | background-color: #252525;
93 | color: #fff;
94 | }
95 |
96 | .active a,
97 | .active i {
98 | color: #fff;
99 | }
100 |
101 | .header-content {
102 | display: flex;
103 | align-items: center;
104 | height: 100%;
105 | justify-content: flex-end;
106 | margin-right: 20px;
107 | }
108 |
109 | .header-content i {
110 | margin-right: 20px;
111 | font-size: 1.2rem;
112 | color: #3d3d3d;
113 | }
114 |
115 | .header-content a {
116 | text-decoration: none;
117 | font-size: 1.1rem;
118 | color: #3d3d3d;
119 | margin: 0 10px;
120 | text-transform: uppercase;
121 | font-weight: 600;
122 | letter-spacing: 1px;
123 | }
124 |
125 | /*****************/
126 | .date-picker {
127 | width: 90%;
128 | margin: 0 auto;
129 | }
130 |
131 | .time-picker {
132 | width: 90%;
133 | margin: 0 auto;
134 | }
135 |
136 | .d-flex-center {
137 | display: flex;
138 | justify-content: center;
139 | }
140 | .appoint-card-body {
141 | display: flex;
142 | background-color: #f7f7f7;
143 | flex-direction: column;
144 | align-items: center;
145 | justify-content: center;
146 | border-radius: 8px;
147 | }
148 | .card-body {
149 | background-color: #f7f7f7;
150 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
151 | width: auto;
152 | }
153 | .user-doctor-admin-name {
154 | margin-top: 8px;
155 | }
156 |
--------------------------------------------------------------------------------
/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Joi = require('joi');
3 | const zxcvbn = require('zxcvbn');
4 |
5 | // Define a schema for user input validation
6 | const userSchema = Joi.object({
7 | name: Joi.string().min(2).max(50).required(),
8 | email: Joi.string().email().required(),
9 | password: Joi.string()
10 | .pattern(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
11 | .min(8)
12 | .max(128)
13 | .required(),
14 | isAdmin: Joi.boolean().optional(),
15 | isDoctor: Joi.boolean().optional(),
16 | notification: Joi.array().items(Joi.object({
17 | type: Joi.string().required(),
18 | message: Joi.string().required(),
19 | data: Joi.object({
20 | doctorId: Joi.string(),
21 | name: Joi.string(),
22 | onClickPath: Joi.string(),
23 | }).optional(),
24 | createdAt: Joi.date().required(),
25 | })).optional(),
26 | seennotification: Joi.array().items(Joi.object({
27 | type: Joi.string().required(),
28 | message: Joi.string().required(),
29 | data: Joi.object({
30 | doctorId: Joi.string(),
31 | name: Joi.string(),
32 | onClickPath: Joi.string(),
33 | }).optional(),
34 | createdAt: Joi.date().required(),
35 | })).optional(),
36 | });
37 |
38 |
39 | // Create a Mongoose schema
40 | const userMongooseSchema = new mongoose.Schema({
41 | name: {
42 | type: String,
43 | required: [true, 'Please provide your name'],
44 | minlength: [2, 'Your name must be at least 2 characters'],
45 | maxlength: [50, 'Your name cannot exceed 50 characters'],
46 | },
47 | email: {
48 | type: String,
49 | required: [true, 'Please provide your email'],
50 | unique: true,
51 | lowercase: true,
52 | },
53 | password: {
54 | type: String,
55 | required: [true, 'Please provide a password'],
56 | validate: [
57 | {
58 | validator: function (value) {
59 | const passwordStrength = zxcvbn(value).score;
60 | return passwordStrength >= 3; // Require a minimum strength of 3 out of 4
61 | },
62 | message: 'Password is too weak',
63 | },
64 | {
65 | validator: function (value) {
66 | return /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/.test(value);
67 | },
68 | message: 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
69 | },
70 | ],
71 | },
72 | isAdmin: {
73 | type: Boolean,
74 | default: false,
75 | },
76 | isDoctor: {
77 | type: Boolean,
78 | default: false,
79 | },
80 | notification: {
81 | type: Array,
82 | default: [],
83 | },
84 | seennotification: {
85 | type: Array,
86 | default: [],
87 | },
88 | createdAt: {
89 | type: Date,
90 | default: Date.now,
91 | },
92 | });
93 |
94 | // Add the Joi validation to the Mongoose schema
95 | userMongooseSchema.validateUser = async function (user) {
96 | return userSchema.validateAsync(user);
97 | };
98 |
99 | // Create a Mongoose model
100 | const User = mongoose.model('User', userMongooseSchema);
101 |
102 | // Export the model
103 | module.exports = User;
--------------------------------------------------------------------------------
/client/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import { Badge, message } from "antd";
2 | import React from "react";
3 | import { useSelector } from "react-redux";
4 | import { Link, useLocation, useNavigate } from "react-router-dom";
5 | import "../styles/LayoutStyles.css";
6 | import { adminMenu, userMenu } from "./../Data/data";
7 |
8 | const Layout = ({ children }) => {
9 | const { user } = useSelector((state) => state.user);
10 | const location = useLocation();
11 | const navigate = useNavigate();
12 |
13 | // logout function
14 | const handleLogout = () => {
15 | localStorage.clear();
16 | message.success("Logout Successfully");
17 | navigate("/login");
18 | };
19 | // =========== doctor menu ===============
20 | const doctorMenu = [
21 | {
22 | name: "Home",
23 | path: "/",
24 | icon: "fa-solid fa-house",
25 | },
26 | {
27 | name: "Appointments",
28 | path: "/doctor-appointments",
29 | icon: "fa-solid fa-list",
30 | },
31 |
32 | {
33 | name: "Profile",
34 | path: `/doctor/profile/${user?._id}`,
35 | icon: "fa-solid fa-user",
36 | },
37 | ];
38 | // =========== doctor menu ===============
39 |
40 | // rendering menu list
41 | const SidebarMenu = user?.isAdmin
42 | ? adminMenu.filter((menu) => menu.name !== "Profile")
43 | : user?.isDoctor
44 | ? doctorMenu
45 | : userMenu.filter((menu) => menu.name !== "Profile");
46 |
47 | return (
48 | <>
49 |
50 |
51 |
52 |
53 |
AppointDoc
54 |
55 |
56 |
57 | {SidebarMenu.map((menu) => {
58 | const isActive = location.pathname === menu.path;
59 | return (
60 |
64 |
65 | {menu.name}
66 |
67 | );
68 | })}
69 |
70 |
71 | Logout
72 |
73 |
74 |
75 |
76 |
77 |
78 | {
83 | navigate("/notification");
84 | }}
85 | >
86 |
87 |
88 |
89 |
90 |
{user?.name}
91 |
92 |
93 |
{children}
94 |
95 |
96 |
97 | >
98 | );
99 | };
100 |
101 | export default Layout;
102 |
--------------------------------------------------------------------------------
/client/build/static/js/main.63fc4e3b.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | Copyright (c) 2018 Jed Watson.
3 | Licensed under the MIT License (MIT), see
4 | http://jedwatson.github.io/classnames
5 | */
6 |
7 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
8 |
9 | /**
10 | * @license React
11 | * react-dom.production.min.js
12 | *
13 | * Copyright (c) Facebook, Inc. and its affiliates.
14 | *
15 | * This source code is licensed under the MIT license found in the
16 | * LICENSE file in the root directory of this source tree.
17 | */
18 |
19 | /**
20 | * @license React
21 | * react-is.production.min.js
22 | *
23 | * Copyright (c) Facebook, Inc. and its affiliates.
24 | *
25 | * This source code is licensed under the MIT license found in the
26 | * LICENSE file in the root directory of this source tree.
27 | */
28 |
29 | /**
30 | * @license React
31 | * react-jsx-runtime.production.min.js
32 | *
33 | * Copyright (c) Facebook, Inc. and its affiliates.
34 | *
35 | * This source code is licensed under the MIT license found in the
36 | * LICENSE file in the root directory of this source tree.
37 | */
38 |
39 | /**
40 | * @license React
41 | * react.production.min.js
42 | *
43 | * Copyright (c) Facebook, Inc. and its affiliates.
44 | *
45 | * This source code is licensed under the MIT license found in the
46 | * LICENSE file in the root directory of this source tree.
47 | */
48 |
49 | /**
50 | * @license React
51 | * scheduler.production.min.js
52 | *
53 | * Copyright (c) Facebook, Inc. and its affiliates.
54 | *
55 | * This source code is licensed under the MIT license found in the
56 | * LICENSE file in the root directory of this source tree.
57 | */
58 |
59 | /**
60 | * @license React
61 | * use-sync-external-store-shim.production.min.js
62 | *
63 | * Copyright (c) Facebook, Inc. and its affiliates.
64 | *
65 | * This source code is licensed under the MIT license found in the
66 | * LICENSE file in the root directory of this source tree.
67 | */
68 |
69 | /**
70 | * @license React
71 | * use-sync-external-store-shim/with-selector.production.min.js
72 | *
73 | * Copyright (c) Facebook, Inc. and its affiliates.
74 | *
75 | * This source code is licensed under the MIT license found in the
76 | * LICENSE file in the root directory of this source tree.
77 | */
78 |
79 | /**
80 | * @remix-run/router v1.6.0
81 | *
82 | * Copyright (c) Remix Software Inc.
83 | *
84 | * This source code is licensed under the MIT license found in the
85 | * LICENSE.md file in the root directory of this source tree.
86 | *
87 | * @license MIT
88 | */
89 |
90 | /**
91 | * React Router DOM v6.11.0
92 | *
93 | * Copyright (c) Remix Software Inc.
94 | *
95 | * This source code is licensed under the MIT license found in the
96 | * LICENSE.md file in the root directory of this source tree.
97 | *
98 | * @license MIT
99 | */
100 |
101 | /**
102 | * React Router v6.11.0
103 | *
104 | * Copyright (c) Remix Software Inc.
105 | *
106 | * This source code is licensed under the MIT license found in the
107 | * LICENSE.md file in the root directory of this source tree.
108 | *
109 | * @license MIT
110 | */
111 |
112 | /** @license React v16.13.1
113 | * react-is.production.min.js
114 | *
115 | * Copyright (c) Facebook, Inc. and its affiliates.
116 | *
117 | * This source code is licensed under the MIT license found in the
118 | * LICENSE file in the root directory of this source tree.
119 | */
120 |
121 | //! moment.js
122 |
--------------------------------------------------------------------------------
/controllers/doctorController.js:
--------------------------------------------------------------------------------
1 | const appointmentModel = require("../models/appointmentModel");
2 | const doctorModel = require("../models/doctorModel");
3 | const userModel = require("../models/userModel");
4 | const getDoctorInfoController = async (req, res) => {
5 | try {
6 | const doctor = await doctorModel.findOne({ userId: req.body.userId });
7 | res.status(200).send({
8 | success: true,
9 | message: "Doctor Data Fetch Success",
10 | data: doctor,
11 | });
12 | } catch (error) {
13 | console.log(error);
14 | res.status(500).send({
15 | success: false,
16 | error,
17 | message: "Error in Fetching Doctor Details",
18 | });
19 | }
20 | };
21 |
22 | // update doc profile
23 | const updateProfileController = async (req, res) => {
24 | try {
25 | const doctor = await doctorModel.findOneAndUpdate(
26 | { userId: req.body.userId },
27 | req.body
28 | );
29 | res.status(201).send({
30 | success: true,
31 | message: "Doctor Profile Updated",
32 | data: doctor,
33 | });
34 | } catch (error) {
35 | console.log(error);
36 | res.status(500).send({
37 | success: false,
38 | message: "Doctor Profile Update Issue",
39 | error,
40 | });
41 | }
42 | };
43 |
44 | //get single docotor
45 | const getDoctorByIdController = async (req, res) => {
46 | try {
47 | const doctor = await doctorModel.findOne({ _id: req.body.doctorId });
48 | res.status(200).send({
49 | success: true,
50 | message: "Single Doctor Info Fetched",
51 | data: doctor,
52 | });
53 | } catch (error) {
54 | console.log(error);
55 | res.status(500).send({
56 | success: false,
57 | error,
58 | message: "Error in Single Doctor Info",
59 | });
60 | }
61 | };
62 |
63 | const doctorAppointmentsController = async (req, res) => {
64 | try {
65 | const doctor = await doctorModel.findOne({ userId: req.body.userId });
66 | const appointments = await appointmentModel.find({
67 | doctorId: doctor._id,
68 | });
69 | res.status(200).send({
70 | success: true,
71 | message: "Doctor Appointments Fetch Successfully",
72 | data: appointments,
73 | });
74 | } catch (error) {
75 | console.log(error);
76 | res.status(500).send({
77 | success: false,
78 | error,
79 | message: "Error In Doctor Appointments",
80 | });
81 | }
82 | };
83 |
84 | const updateStatusController = async (req, res) => {
85 | try {
86 | const { appointmentsId, status } = req.body;
87 | const appointments = await appointmentModel.findByIdAndUpdate(
88 | appointmentsId,
89 | { status }
90 | );
91 | const user = await userModel.findOne({ _id: appointments.userId });
92 | let notification = user.notification || []; // Initialize notifcation to an empty array if it's undefined
93 | notification.push({
94 | type: "status-updated",
95 | message: `Your Appointment Has Been Updated ${status}`,
96 | onCLickPath: "/doctor-appointments",
97 | });
98 | await userModel.updateOne(
99 | { _id: user._id },
100 | { $set: { notification: notification } }
101 | );
102 | res.status(200).send({
103 | success: true,
104 | message: "Appointment Status Updated",
105 | });
106 | } catch (error) {
107 | console.log(error);
108 | res.status(500).send({
109 | success: false,
110 | error,
111 | message: "Error In Update Status",
112 | });
113 | }
114 | };
115 |
116 |
117 | module.exports = { getDoctorInfoController, updateProfileController, getDoctorByIdController, doctorAppointmentsController, updateStatusController };
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { BrowserRouter, Route, Routes } from "react-router-dom";
3 | import ProtectedRoute from "./components/ProtectedRoute";
4 | import PublicRoute from "./components/PublicRoute";
5 | import Spinner from "./components/Spinner";
6 | import ApplyDoctor from "./pages/ApplyDoctor";
7 | import Appointments from "./pages/Appointments";
8 | import BookingPage from "./pages/BookingPage";
9 | import HomePage from "./pages/HomePage";
10 | import Login from "./pages/Login";
11 | import NotificationPage from "./pages/NotificationPage";
12 | import Register from "./pages/Register";
13 | import Doctors from "./pages/admin/Doctors";
14 | import Users from "./pages/admin/Users";
15 | import DoctorAppointments from "./pages/doctor/DoctorAppointments";
16 | import Profile from "./pages/doctor/Profile";
17 |
18 | function App() {
19 | const { loading } = useSelector((state) => state.alerts);
20 | return (
21 | <>
22 |
23 | {loading ? (
24 |
25 | ) : (
26 |
27 |
31 |
32 |
33 | }
34 | />
35 |
39 |
40 |
41 | }
42 | />
43 |
47 |
48 |
49 | }
50 | />
51 |
55 |
56 |
57 | }
58 | />
59 |
63 |
64 |
65 | }
66 | />
67 |
71 |
72 |
73 | }
74 | />
75 |
79 |
80 |
81 | }
82 | />
83 |
87 |
88 |
89 | }
90 | />
91 |
95 |
96 |
97 | }
98 | />
99 |
103 |
104 |
105 | }
106 | />
107 |
111 |
112 |
113 | }
114 | />
115 |
116 | )}
117 |
118 | >
119 | );
120 | }
121 |
122 | export default App;
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppointDoc
2 |
3 | This is a professional-grade MERN (MongoDB, Express, React, Node.js) stack web application for booking doctor appointments. Patients can browse through a list of doctors and their availability and book an appointment with their preferred doctor. Doctors can view their schedule, manage their availability, and approve/cancel appointments.
4 |
5 | ## Project View
6 |
7 | 1. For User Profile
8 |
9 | - **Homepage**
10 |
11 |
12 |
13 |
14 | - **Appointment Lists**
15 |
16 |
17 |
18 |
19 | - **Booking Appointment**
20 |
21 |
22 |
23 |
24 | - **Apply As Doctor**
25 |
26 |
27 |
28 |
29 | - **New Notifications**
30 |
31 |
32 |
33 |
34 | - **Read Notifications**
35 |
36 |
37 |
38 |
39 | 2. For Doctor Profile
40 |
41 | - **Homepage**
42 |
43 |
44 |
45 |
46 | - **Appointment Lists**
47 |
48 |
49 |
50 |
51 | - **Manage Profile**
52 |
53 |
54 |
55 |
56 | 3. For Admin Profile
57 |
58 | - **Homepage**
59 |
60 |
61 |
62 |
63 | - **Doctors List**
64 |
65 |
66 |
67 |
68 | - **Users List**
69 |
70 |
71 |
72 |
73 | ## Installation
74 |
75 | To set up BloodLife locally, follow these steps:
76 |
77 | - Clone the repository:
78 |
79 | git clone https://github.com/OviSarkar62/AppointDoc.git
80 |
81 | - Install the required dependencies for backend:
82 |
83 | npm install express joi jsonwebtoken moment mongoose morgan nodemon zxcvbn dotenv colors bcryptjs
84 |
85 | - Navigate to the client directory:
86 |
87 | cd client
88 |
89 | - Install the dependencies for the client:
90 |
91 | npm i react-router-dom react-redux axios antd @reduxjs/toolkit react-bootstrap moment
92 |
93 | - Create a .env file in the root directory with the following environment variables:
94 |
95 | DB_URL = mongodb+srv://:@cluster0.l17quyr.mongodb.net/database
96 |
97 | JWT_SECRET = A_Secret_Value
98 |
99 | PORT = 4000
100 |
101 | - Start the server:
102 |
103 | npm start
104 |
105 | - In a new terminal window, navigate to the client directory:
106 |
107 | cd client
108 |
109 | - Start the client:
110 |
111 | npm start
112 |
113 | - Access the application. Open your web browser and visit http://localhost:3000 to access the application.
114 |
115 |
116 | ## Usage
117 |
118 | - The AppointDoc application allows doctors to manage their appointments with ease. Doctors can create new appointments, view existing appointments, and approve or reject appointments as necessary.
119 |
120 | - To create a new appointment, the user needs to click on the "New Appointment" button on the doctor's card. They will then be presented with a form where they can enter the details of the appointment, including date and time.
121 |
122 | - To view existing appointments, the doctor needs to click on the "Appointments" button on the sidebar. They will then be presented with a list of all their appointments, presented by date and time. The doctor can click on any appointment to approve or reject to change it's pending status.
123 |
124 | - To update time of availability, the doctor needs to click on the profile page where they wish to update the time of appointments. They will then be presented with a form where they can edit the start time and end time details of the appointment.
125 |
126 | ## Stack
127 |
128 | - MongoDB - NoSQL database for storing data
129 | - Express - Backend framework for building RESTful APIs
130 | - React - Frontend framework for building user interfaces
131 | - Node.js - JavaScript runtime environment for building scalable server-side applications
132 | - JWT - JSON Web Token for user authentication and authorization
133 | - Bcrypt - Password hashing library for secure password storage
134 |
135 | ## Live Link
136 |
137 | The live project: [AppointDoc](https://appoint-doc.vercel.app/login)
138 |
--------------------------------------------------------------------------------
/client/build/static/css/main.9cc7b55e.css:
--------------------------------------------------------------------------------
1 | body,html{height:100%;width:100%}input::-ms-clear,input::-ms-reveal{display:none}*,:after,:before{box-sizing:border-box}html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:sans-serif;line-height:1.15}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:initial;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{font-weight:500;margin-bottom:.5em;margin-top:0}p{margin-bottom:1em;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;-webkit-text-decoration:underline dotted;text-decoration:underline;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1em}input[type=number],input[type=password],input[type=text],textarea{-webkit-appearance:none}dl,ol,ul{margin-bottom:1em;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}code,kbd,pre,samp{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:1em}pre{margin-bottom:1em;margin-top:0;overflow:auto}figure{margin:0 0 1em}img{border-style:none;vertical-align:middle}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{caption-side:bottom;padding-bottom:.3em;padding-top:.75em;text-align:left}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:inherit;line-height:inherit;margin:0}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{border-style:none;padding:0}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{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5em;line-height:inherit;margin-bottom:.5em;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:initial}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{background-color:#feffe6;padding:.2em}.main{background-color:#fff;color:#3d3d3d;height:100vh;padding:20px}.layout{display:flex}.sidebar{background-color:#f7f7f7;border-radius:8px;box-shadow:0 0 2px rgba(0,0,0,.1);color:#3d3d3d;font-size:1.1rem;font-weight:600;height:auto;margin-right:20px;min-height:760px;width:300px}.content{height:100%;width:100%}.header{align-items:center;display:flex;height:60px;padding:0 20px}.body,.header{background-color:#fff;border-radius:8px;box-shadow:0 0 2px rgba(0,0,0,.1);margin-bottom:20px}.body{height:calc(100% - 80px);overflow-y:auto;padding:20px}.logo h6{color:#3d3d3d;font-size:1.5rem;font-weight:700;letter-spacing:2px;margin:20px 0;text-align:center}.menu{margin-top:50px}.menu-item{align-items:center;border-radius:8px;cursor:pointer;display:flex;margin-top:25px;padding:10px 20px;transition:all .3s ease}.menu-item:hover{background-color:grey}.menu-item a{color:#3d3d3d;margin-left:20px;text-decoration:none}.menu-item i{color:#3d3d3d;font-size:1.2rem;margin-right:20px}.active{background-color:#252525}.active,.active a,.active i{color:#fff}.header-content{align-items:center;display:flex;height:100%;justify-content:flex-end;margin-right:20px}.header-content i{color:#3d3d3d;font-size:1.2rem;margin-right:20px}.header-content a{color:#3d3d3d;font-size:1.1rem;font-weight:600;letter-spacing:1px;margin:0 10px;text-decoration:none;text-transform:uppercase}.date-picker,.time-picker{margin:0 auto;width:90%}.appoint-card-body,.d-flex-center{display:flex;justify-content:center}.appoint-card-body{align-items:center;background-color:#f7f7f7;border-radius:8px;flex-direction:column}.card-body{background-color:#f7f7f7;box-shadow:0 0 2px rgba(0,0,0,.1);width:auto}.user-doctor-admin-name{margin-top:8px}.spinner{left:50%;position:absolute;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);z-index:100}.spinner-border{height:50px;width:50px}.register-page{align-items:center;display:flex;height:90vh;justify-content:center}.register-page form{background-color:#fff;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.2);padding:40px;width:400px}.register-page form .d-flex{align-items:center;display:flex;justify-content:space-between;margin-top:20px}.register-page form .d-flex a{color:#007bff;text-decoration:none}.register-page h1{text-align:center}.register-page .ant-form-item,.register-page h1{margin-bottom:20px}.register-page .ant-form-item-label{font-weight:700}.register-page .ant-form-item-explain{color:#ff4d4f}.register-page .ant-form-item-has-error .ant-input{border-color:#ff4d4f}.register-page .ant-form-item-has-error .ant-input:focus{box-shadow:0 0 0 2px rgba(255,77,79,.2)}.register-page .btn-primary{width:100%}
2 | /*# sourceMappingURL=main.9cc7b55e.css.map*/
--------------------------------------------------------------------------------
/client/src/pages/BookingPage.js:
--------------------------------------------------------------------------------
1 | import { DatePicker, TimePicker, message } from "antd";
2 | import axios from "axios";
3 | import React, { useEffect, useState } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { useParams } from "react-router-dom";
6 | import Layout from "../components/Layout";
7 | import { hideLoading, showLoading } from "../redux/features/alertSlice";
8 | import "./../styles/LayoutStyles.css";
9 |
10 | const BookingPage = () => {
11 | const { user } = useSelector((state) => state.user);
12 | const params = useParams();
13 | const [doctors, setDoctors] = useState([]);
14 | const [date, setDate] = useState("");
15 | const [time, setTime] = useState("");
16 | const [isAvailable, setIsAvailable] = useState();
17 | const dispatch = useDispatch();
18 | // login user data
19 | const getUserData = async () => {
20 | try {
21 | const res = await axios.post(
22 | "/api/doctor/getDoctorById",
23 | { doctorId: params.doctorId },
24 | {
25 | headers: {
26 | Authorization: "Bearer " + localStorage.getItem("token"),
27 | },
28 | }
29 | );
30 | if (res.data.success) {
31 | setDoctors(res.data.data);
32 | }
33 | } catch (error) {
34 | console.log(error);
35 | }
36 | };
37 |
38 | // handleAvailability function
39 | const handleAvailability = async () => {
40 | try {
41 | dispatch(showLoading());
42 | const res = await axios.post(
43 | "/api/user/booking-availbility",
44 | {
45 | doctorId: params.doctorId,
46 | date,
47 | time,
48 | },
49 | {
50 | headers: {
51 | Authorization: `Bearer ${localStorage.getItem("token")}`,
52 | },
53 | }
54 | );
55 | dispatch(hideLoading());
56 | if (res.data.success) {
57 | setIsAvailable(true);
58 | console.log(isAvailable);
59 | message.success(res.data.message);
60 | } else {
61 | message.error(res.data.message);
62 | }
63 | } catch (error) {
64 | dispatch(hideLoading());
65 | console.log(error);
66 | }
67 | };
68 |
69 | // =============== booking func
70 | const handleBooking = async () => {
71 | try {
72 | setIsAvailable(true);
73 | if (!date && !time) {
74 | return alert("Date & Time Required");
75 | }
76 | dispatch(showLoading());
77 | const res = await axios.post(
78 | "/api/user/book-appointment",
79 | {
80 | doctorId: params.doctorId,
81 | userId: user._id,
82 | doctorInfo: doctors,
83 | userInfo: user,
84 | date: date,
85 | time: time,
86 | },
87 | {
88 | headers: {
89 | Authorization: `Bearer ${localStorage.getItem("token")}`,
90 | },
91 | }
92 | );
93 | dispatch(hideLoading());
94 | if (res.data.success) {
95 | message.success(res.data.message);
96 | } else {
97 | message.error(res.data.message);
98 | }
99 | } catch (error) {
100 | dispatch(hideLoading());
101 | console.log(error);
102 | }
103 | };
104 |
105 |
106 | useEffect(() => {
107 | getUserData();
108 | //eslint-disable-next-line
109 | }, []);
110 |
111 | return (
112 |
113 |
114 |
Book an Appointment
115 | {doctors && (
116 |
117 |
118 |
119 | Dr. {doctors.firstName} {doctors.lastName}
120 |
121 |
122 | Fees: {doctors.feesPerConsultation}
123 |
124 |
125 | Timings: {doctors.starttime} - {doctors.endtime}
126 |
127 |
128 |
129 |
{
133 | const selectedDate = value
134 | ? value.format("DD-MM-YYYY")
135 | : "";
136 | setDate(selectedDate);
137 | }}
138 | />
139 | setTime(time && time.format("HH:mm"))}
143 | />
144 |
145 |
151 |
152 |
153 |
159 |
160 |
161 |
162 |
163 |
164 | )}
165 |
166 |
167 | );
168 | };
169 |
170 | export default BookingPage;
171 |
--------------------------------------------------------------------------------
/client/src/pages/NotificationPage.js:
--------------------------------------------------------------------------------
1 | import { message, Tabs } from "antd";
2 | import axios from "axios";
3 | import React, { useState } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { useNavigate } from "react-router-dom";
6 | import {
7 | hideLoading,
8 | showLoading
9 | } from "../redux/features/alertSlice";
10 | import { setUser } from "../redux/features/userSlice";
11 | import Layout from "./../components/Layout";
12 |
13 | const NotificationPage = () => {
14 | const dispatch = useDispatch();
15 | const navigate = useNavigate();
16 | const { user } = useSelector((state) => state.user);
17 | const [refreshNotifications, setRefreshNotifications] = useState(false);
18 |
19 |
20 | // handle read notification
21 | const handleMarkAllRead = async () => {
22 | try {
23 | dispatch(showLoading());
24 | const res = await axios.post(
25 | "/api/user/get-all-notification",
26 | {
27 | userId: user._id,
28 | },
29 | {
30 | headers: {
31 | Authorization: `Bearer ${localStorage.getItem("token")}`,
32 | },
33 | }
34 | );
35 | dispatch(hideLoading());
36 | if (res.data.success) {
37 | const unreadNotifications = res.data.notifications
38 | ? res.data.notifications.filter(
39 | (notification) => !notification.isRead
40 | )
41 | : [];
42 | updateNotificationsInStore(unreadNotifications);
43 | setRefreshNotifications(!refreshNotifications);
44 | window.location.reload();
45 | message.success(res.data.message);
46 | } else {
47 | message.error(res.data.message);
48 | }
49 | } catch (error) {
50 | dispatch(hideLoading());
51 | console.log(error);
52 | message.error("Something Went Wrong");
53 | }
54 | };
55 |
56 |
57 | // handle delete notification
58 | const handleDeleteAllRead = async () => {
59 | try {
60 | dispatch(showLoading());
61 | const res = await axios.post(
62 | "/api/user/delete-all-notification",
63 | {
64 | userId: user._id,
65 | },
66 | {
67 | headers: {
68 | Authorization: `Bearer ${localStorage.getItem("token")}`,
69 | },
70 | }
71 | );
72 | dispatch(hideLoading());
73 | if (res.data.success) {
74 | const updatedNotifications = res.data.notifications || []; // Add a default value of an empty array if the notifications array is undefined
75 | const updatedUser = {
76 | ...user,
77 | notification: updatedNotifications.filter(notification => !notification.isRead),
78 | seennotification: updatedNotifications.filter(notification => notification.isRead),
79 | };
80 | dispatch(setUser(updatedUser));
81 | setRefreshNotifications(!refreshNotifications);
82 | message.success(res.data.message);
83 | } else {
84 | message.error(res.data.message);
85 | }
86 | } catch (error) {
87 | dispatch(hideLoading());
88 | console.log(error);
89 | message.error("Something Went Wrong");
90 | }
91 | };
92 |
93 |
94 | const updateNotificationsInStore = (notifications) => {
95 | const updatedUser = {
96 | ...user,
97 | notification: [],
98 | seennotification: [],
99 | };
100 | notifications.forEach((notification) => {
101 | if (notification.isRead) {
102 | updatedUser.seennotification.push(notification);
103 | } else {
104 | updatedUser.notification.push(notification);
105 | }
106 | });
107 | dispatch(setUser(updatedUser));
108 | };
109 |
110 |
111 | return (
112 |
113 | Notifications
114 |
115 |
116 |
117 |
124 |
125 | {user && user.notification && user.notification.length > 0 ? (
126 | user.notification.map((notificationMgs) => (
127 |
132 |
{
135 | navigate(notificationMgs.onClickPath);
136 | }}
137 | >
138 | {notificationMgs.message}
139 |
140 |
141 | ))
142 | ) : (
143 | You have no new notifications.
144 | )}
145 |
146 |
147 |
148 |
155 |
156 | {user && user.seennotification && user.seennotification.length > 0 ? (
157 | user.seennotification.map((notificationMgs) => (
158 |
163 |
navigate(notificationMgs.onClickPath)}
166 | >
167 | {notificationMgs.message}
168 |
169 |
170 | ))
171 | ) : (
172 | You have no read notifications
173 | )}
174 |
175 |
176 |
177 | );
178 | };
179 |
180 | export default NotificationPage;
181 |
--------------------------------------------------------------------------------
/client/src/pages/ApplyDoctor.js:
--------------------------------------------------------------------------------
1 | import { Col, Form, Input, Row, TimePicker, message } from "antd";
2 | import axios from "axios";
3 | import React from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { useNavigate } from "react-router-dom";
6 | import { hideLoading, showLoading } from "../redux/features/alertSlice";
7 | import Layout from "./../components/Layout";
8 |
9 | const ApplyDoctor = () => {
10 | const { user } = useSelector((state) => state.user);
11 |
12 | const dispatch = useDispatch();
13 | const navigate = useNavigate();
14 |
15 | const handleFinish = async (values) => {
16 | try {
17 | dispatch(showLoading());
18 | const starttime = values.starttime.format("HH:mm");
19 | const endtime = values.endtime.format("HH:mm");
20 | const res = await axios.post(
21 | "/api/user/apply-doctor",
22 | {
23 | ...values,
24 | userId: user._id,
25 | starttime,
26 | endtime,
27 | },
28 | {
29 | headers: {
30 | Authorization: `Bearer ${localStorage.getItem("token")}`,
31 | },
32 | }
33 | );
34 | dispatch(hideLoading());
35 | if (res.data.success) {
36 | message.success(res.data.message);
37 | navigate("/");
38 | } else {
39 | message.error(res.data.message);
40 | }
41 | } catch (error) {
42 | dispatch(hideLoading());
43 | console.log(error);
44 | message.error("Something Went Wrong");
45 | }
46 | };
47 |
48 | return (
49 |
50 | Apply Doctor
51 |
172 |
173 | );
174 | };
175 |
176 | export default ApplyDoctor;
177 |
--------------------------------------------------------------------------------
/client/src/pages/doctor/Profile.js:
--------------------------------------------------------------------------------
1 | import { Col, Form, Input, Row, TimePicker, message } from "antd";
2 | import axios from "axios";
3 | import moment from "moment/moment";
4 | import React, { useEffect, useState } from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { useNavigate, useParams } from "react-router-dom";
7 | import { hideLoading, showLoading } from "../../redux/features/alertSlice";
8 | import Layout from "./../../components/Layout";
9 |
10 |
11 | const Profile = () => {
12 | const { user } = useSelector((state) => state.user);
13 | const [doctor, setDoctor] = useState(null);
14 | const dispatch = useDispatch();
15 | const navigate = useNavigate();
16 | const params = useParams();
17 | // update doc ==========
18 | //handle form
19 | const handleFinish = async (values) => {
20 | try {
21 | dispatch(showLoading());
22 | const starttime = values.starttime.format("HH:mm");
23 | const endtime = values.endtime.format("HH:mm");
24 | const res = await axios.post(
25 | "/api/doctor/updateProfile",
26 | {
27 | ...values,
28 | userId: user._id,
29 | starttime,
30 | endtime,
31 | },
32 | {
33 | headers: {
34 | Authorization: `Bearer ${localStorage.getItem("token")}`,
35 | },
36 | }
37 | );
38 | dispatch(hideLoading());
39 | if (res.data.success) {
40 | message.success(res.data.message);
41 | navigate("/");
42 | } else {
43 | message.error(res.data.message);
44 | }
45 | } catch (error) {
46 | dispatch(hideLoading());
47 | console.log(error);
48 | message.error("Somthing Went Wrong ");
49 | }
50 | };
51 | // update doc ==========
52 |
53 | //getDOc Details
54 | const getDoctorInfo = async () => {
55 | try {
56 | const res = await axios.post(
57 | "/api/doctor/getDoctorInfo",
58 | { userId: params.id },
59 | {
60 | headers: {
61 | Authorization: `Bearer ${localStorage.getItem("token")}`,
62 | },
63 | }
64 | );
65 | if (res.data.success) {
66 | setDoctor(res.data.data);
67 | }
68 | } catch (error) {
69 | console.log(error);
70 | }
71 | };
72 |
73 | useEffect(() => {
74 | getDoctorInfo();
75 | //eslint-disable-next-line
76 | }, []);
77 | return (
78 |
79 | Manage Profile
80 | {doctor && (
81 |
207 | )}
208 |
209 | );
210 | };
211 |
212 | export default Profile;
213 |
--------------------------------------------------------------------------------
/client/build/static/css/main.9cc7b55e.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"static/css/main.9cc7b55e.css","mappings":"AACA,UAGE,WAAY,CADZ,UAEF,CACA,mCAEE,YACF,CACA,iBAGE,qBACF,CACA,KAGE,6BAA8B,CAC9B,yBAA0B,CAC1B,4BAA6B,CAC7B,yCAA6C,CAL7C,sBAAuB,CACvB,gBAKF,CAIA,KACE,QACF,CACA,sBACE,YACF,CACA,GACE,kBAAuB,CACvB,QAAS,CACT,gBACF,CACA,kBAQE,eAAgB,CADhB,kBAAoB,CADpB,YAGF,CACA,EAEE,iBAAkB,CADlB,YAEF,CACA,sCAKE,eAAgB,CAChB,WAAY,CAJZ,wCAAyC,CACzC,yBAA0B,CAC1B,gCAGF,CACA,QAEE,iBAAkB,CAClB,mBAAoB,CAFpB,iBAGF,CACA,kEAIE,uBACF,CACA,SAIE,iBAAkB,CADlB,YAEF,CACA,wBAIE,eACF,CACA,GACE,eACF,CACA,GACE,kBAAoB,CACpB,aACF,CACA,WACE,cACF,CACA,IACE,iBACF,CACA,SAEE,kBACF,CACA,MACE,aACF,CACA,QAGE,aAAc,CACd,aAAc,CAFd,iBAAkB,CAGlB,sBACF,CACA,IACE,aACF,CACA,IACE,SACF,CACA,kBAKE,2EAAqF,CADrF,aAEF,CACA,IAEE,iBAAkB,CADlB,YAAa,CAEb,aACF,CACA,OACE,cACF,CACA,IAEE,iBAAkB,CADlB,qBAEF,CACA,kFASE,yBACF,CACA,MACE,wBACF,CACA,QAIE,mBAAoB,CAFpB,mBAAqB,CADrB,iBAAmB,CAEnB,eAEF,CACA,sCAME,aAAc,CAEd,mBAAoB,CADpB,iBAAkB,CAElB,mBAAoB,CAJpB,QAKF,CACA,aAEE,gBACF,CACA,cAEE,mBACF,CACA,qDAIE,yBACF,CACA,wHAKE,iBAAkB,CADlB,SAEF,CACA,uCAEE,qBAAsB,CACtB,SACF,CACA,+EAIE,0BACF,CACA,SACE,aAAc,CACd,eACF,CACA,SAIE,QAAS,CAFT,QAAS,CADT,WAAY,CAEZ,SAEF,CACA,OAME,aAAc,CALd,aAAc,CAMd,eAAgB,CAChB,mBAAoB,CAJpB,kBAAoB,CADpB,cAAe,CAEf,SAAU,CAIV,kBAAmB,CAPnB,UAQF,CACA,SACE,sBACF,CACA,kFAEE,WACF,CACA,cAEE,uBAAwB,CADxB,mBAEF,CACA,qFAEE,uBACF,CACA,6BAEE,yBAA0B,CAD1B,YAEF,CACA,OACE,oBACF,CACA,QACE,iBACF,CACA,SACE,YACF,CACA,SACE,sBACF,CACA,KAEE,wBAAyB,CADzB,YAEF,CC3PA,MAGE,qBAAsB,CACtB,aAAc,CAFd,YAAa,CADb,YAIF,CAEA,QACE,YACF,CAEA,SAKE,wBAAyB,CADzB,iBAAkB,CAElB,iCAAsC,CAEtC,aAAc,CACd,gBAAiB,CACjB,eAAgB,CARhB,WAAY,CAKZ,iBAAkB,CANlB,gBAAiB,CAEjB,WAQF,CAEA,SAEE,WAAY,CADZ,UAEF,CAEA,QAOE,kBAAmB,CADnB,YAAa,CALb,WAAY,CAOZ,cACF,CAEA,cANE,qBAAsB,CAFtB,iBAAkB,CAClB,iCAAsC,CAFtC,kBAiBF,CARA,MACE,wBAAyB,CAKzB,eAAgB,CAChB,YACF,CAEA,SAIE,aAAc,CAHd,gBAAiB,CAIjB,eAAgB,CAChB,kBAAmB,CAHnB,aAAgB,CADhB,iBAKF,CAEA,MACE,eACF,CAEA,WAGE,kBAAmB,CAEnB,iBAAkB,CAElB,cAAe,CALf,YAAa,CADb,eAAgB,CAGhB,iBAAkB,CAElB,uBAEF,CAEA,iBACE,qBACF,CAEA,aACE,aAAc,CAEd,gBAAiB,CADjB,oBAEF,CAEA,aAGE,aAAc,CAFd,gBAAiB,CACjB,iBAEF,CAEA,QACE,wBAEF,CAEA,4BAHE,UAMF,CAEA,gBAEE,kBAAmB,CADnB,YAAa,CAEb,WAAY,CACZ,wBAAyB,CACzB,iBACF,CAEA,kBAGE,aAAc,CADd,gBAAiB,CADjB,iBAGF,CAEA,kBAGE,aAAc,CADd,gBAAiB,CAIjB,eAAgB,CAChB,kBAAmB,CAHnB,aAAc,CAHd,oBAAqB,CAIrB,wBAGF,CAQA,0BAEE,aAAc,CADd,SAEF,CAMA,kCAHE,YAAa,CACb,sBASF,CAPA,mBAIE,kBAAmB,CAFnB,wBAAyB,CAIzB,iBAAkB,CAHlB,qBAIF,CACA,WACE,wBAAyB,CACzB,iCAAsC,CACtC,UACF,CACA,wBACE,cACF,CC1JA,SAGE,QAAS,CAFT,iBAAkB,CAClB,OAAQ,CAER,sCAAgC,CAAhC,8BAAgC,CAChC,WACF,CAEA,gBACE,WAAY,CACZ,UACF,CAIA,eAEE,kBAAmB,CADnB,YAAa,CAGb,WAAY,CADZ,sBAEF,CAEA,oBAEE,qBAAsB,CAEtB,kBAAmB,CACnB,kCAAuC,CAFvC,YAAa,CAFb,WAKF,CACA,4BAGE,kBAAmB,CAFnB,YAAa,CACb,6BAA8B,CAE9B,eACF,CAEA,8BACE,aAAc,CACd,oBACF,CAEA,kBACE,iBAEF,CAEA,gDAHE,kBAKF,CAEA,oCACE,eACF,CAEA,sCACE,aACF,CAEA,mDACE,oBACF,CAEA,yDACE,uCACF,CAEA,4BACE,UACF","sources":["../node_modules/antd/dist/reset.css","styles/LayoutStyles.css","index.css"],"sourcesContent":["/* stylelint-disable */\nhtml,\nbody {\n width: 100%;\n height: 100%;\n}\ninput::-ms-clear,\ninput::-ms-reveal {\n display: none;\n}\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -ms-text-size-adjust: 100%;\n -ms-overflow-style: scrollbar;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n@-ms-viewport {\n width: device-width;\n}\nbody {\n margin: 0;\n}\n[tabindex='-1']:focus {\n outline: none;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n margin-top: 0;\n margin-bottom: 0.5em;\n font-weight: 500;\n}\np {\n margin-top: 0;\n margin-bottom: 1em;\n}\nabbr[title],\nabbr[data-original-title] {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline;\n text-decoration: underline dotted;\n border-bottom: 0;\n cursor: help;\n}\naddress {\n margin-bottom: 1em;\n font-style: normal;\n line-height: inherit;\n}\ninput[type='text'],\ninput[type='password'],\ninput[type='number'],\ntextarea {\n -webkit-appearance: none;\n}\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1em;\n}\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\ndt {\n font-weight: 500;\n}\ndd {\n margin-bottom: 0.5em;\n margin-left: 0;\n}\nblockquote {\n margin: 0 0 1em;\n}\ndfn {\n font-style: italic;\n}\nb,\nstrong {\n font-weight: bolder;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\npre,\ncode,\nkbd,\nsamp {\n font-size: 1em;\n font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;\n}\npre {\n margin-top: 0;\n margin-bottom: 1em;\n overflow: auto;\n}\nfigure {\n margin: 0 0 1em;\n}\nimg {\n vertical-align: middle;\n border-style: none;\n}\na,\narea,\nbutton,\n[role='button'],\ninput:not([type='range']),\nlabel,\nselect,\nsummary,\ntextarea {\n touch-action: manipulation;\n}\ntable {\n border-collapse: collapse;\n}\ncaption {\n padding-top: 0.75em;\n padding-bottom: 0.3em;\n text-align: left;\n caption-side: bottom;\n}\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n color: inherit;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\nbutton,\ninput {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml [type='button'],\n[type='reset'],\n[type='submit'] {\n -webkit-appearance: button;\n}\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\ninput[type='radio'],\ninput[type='checkbox'] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type='date'],\ninput[type='time'],\ninput[type='datetime-local'],\ninput[type='month'] {\n -webkit-appearance: listbox;\n}\ntextarea {\n overflow: auto;\n resize: vertical;\n}\nfieldset {\n min-width: 0;\n margin: 0;\n padding: 0;\n border: 0;\n}\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n margin-bottom: 0.5em;\n padding: 0;\n color: inherit;\n font-size: 1.5em;\n line-height: inherit;\n white-space: normal;\n}\nprogress {\n vertical-align: baseline;\n}\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n height: auto;\n}\n[type='search'] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n[type='search']::-webkit-search-cancel-button,\n[type='search']::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\noutput {\n display: inline-block;\n}\nsummary {\n display: list-item;\n}\ntemplate {\n display: none;\n}\n[hidden] {\n display: none !important;\n}\nmark {\n padding: 0.2em;\n background-color: #feffe6;\n}\n","/* Light mode styles */\r\n.main {\r\n padding: 20px;\r\n height: 100vh;\r\n background-color: #fff;\r\n color: #3d3d3d;\r\n}\r\n\r\n.layout {\r\n display: flex;\r\n}\r\n\r\n.sidebar {\r\n min-height: 760px;\r\n height: auto;\r\n width: 300px;\r\n border-radius: 8px;\r\n background-color: #f7f7f7;\r\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);\r\n margin-right: 20px;\r\n color: #3d3d3d;\r\n font-size: 1.1rem;\r\n font-weight: 600;\r\n}\r\n\r\n.content {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.header {\r\n height: 60px;\r\n margin-bottom: 20px;\r\n border-radius: 8px;\r\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);\r\n background-color: #fff;\r\n display: flex;\r\n align-items: center;\r\n padding: 0 20px;\r\n}\r\n\r\n.body {\r\n height: calc(100% - 80px);\r\n border-radius: 8px;\r\n margin-bottom: 20px;\r\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);\r\n background-color: #fff;\r\n overflow-y: auto;\r\n padding: 20px;\r\n}\r\n\r\n.logo h6 {\r\n font-size: 1.5rem;\r\n text-align: center;\r\n margin: 20px 0px;\r\n color: #3d3d3d;\r\n font-weight: 700;\r\n letter-spacing: 2px;\r\n}\r\n\r\n.menu {\r\n margin-top: 50px;\r\n}\r\n\r\n.menu-item {\r\n margin-top: 25px;\r\n display: flex;\r\n align-items: center;\r\n padding: 10px 20px;\r\n border-radius: 8px;\r\n transition: all 0.3s ease;\r\n cursor: pointer;\r\n}\r\n\r\n.menu-item:hover {\r\n background-color: grey;\r\n}\r\n\r\n.menu-item a {\r\n color: #3d3d3d;\r\n text-decoration: none;\r\n margin-left: 20px;\r\n}\r\n\r\n.menu-item i {\r\n font-size: 1.2rem;\r\n margin-right: 20px;\r\n color: #3d3d3d;\r\n}\r\n\r\n.active {\r\n background-color: #252525;\r\n color: #fff;\r\n}\r\n\r\n.active a,\r\n.active i {\r\n color: #fff;\r\n}\r\n\r\n.header-content {\r\n display: flex;\r\n align-items: center;\r\n height: 100%;\r\n justify-content: flex-end;\r\n margin-right: 20px;\r\n}\r\n\r\n.header-content i {\r\n margin-right: 20px;\r\n font-size: 1.2rem;\r\n color: #3d3d3d;\r\n}\r\n\r\n.header-content a {\r\n text-decoration: none;\r\n font-size: 1.1rem;\r\n color: #3d3d3d;\r\n margin: 0 10px;\r\n text-transform: uppercase;\r\n font-weight: 600;\r\n letter-spacing: 1px;\r\n}\r\n\r\n/*****************/\r\n.date-picker {\r\n width: 90%;\r\n margin: 0 auto;\r\n}\r\n\r\n.time-picker {\r\n width: 90%;\r\n margin: 0 auto;\r\n}\r\n\r\n.d-flex-center {\r\n display: flex;\r\n justify-content: center;\r\n}\r\n.appoint-card-body {\r\n display: flex;\r\n background-color: #f7f7f7;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n}\r\n.card-body {\r\n background-color: #f7f7f7;\r\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);\r\n width: auto;\r\n}\r\n.user-doctor-admin-name {\r\n margin-top: 8px;\r\n}\r\n",".spinner {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n z-index: 100;\r\n}\r\n\r\n.spinner-border {\r\n height: 50px;\r\n width: 50px;\r\n}\r\n\r\n/*register page*/\r\n\r\n.register-page {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: 90vh;\r\n}\r\n\r\n.register-page form {\r\n width: 400px;\r\n background-color: #fff;\r\n padding: 40px;\r\n border-radius: 10px;\r\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);\r\n}\r\n.register-page form .d-flex {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-top: 20px;\r\n}\r\n\r\n.register-page form .d-flex a {\r\n color: #007bff;\r\n text-decoration: none;\r\n}\r\n\r\n.register-page h1 {\r\n text-align: center;\r\n margin-bottom: 20px;\r\n}\r\n\r\n.register-page .ant-form-item {\r\n margin-bottom: 20px;\r\n}\r\n\r\n.register-page .ant-form-item-label {\r\n font-weight: bold;\r\n}\r\n\r\n.register-page .ant-form-item-explain {\r\n color: #ff4d4f;\r\n}\r\n\r\n.register-page .ant-form-item-has-error .ant-input {\r\n border-color: #ff4d4f;\r\n}\r\n\r\n.register-page .ant-form-item-has-error .ant-input:focus {\r\n box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);\r\n}\r\n\r\n.register-page .btn-primary {\r\n width: 100%;\r\n}\r\n"],"names":[],"sourceRoot":""}
--------------------------------------------------------------------------------
/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcryptjs");
2 | const userModel = require('../models/userModel');
3 | const doctorModel = require("../models/doctorModel");
4 | const appointmentModel = require("../models/appointmentModel");
5 | const jwt = require("jsonwebtoken");
6 | const moment = require("moment");
7 |
8 | // login callback
9 | const loginController = async (req, res) => {
10 | try {
11 | const { email, password } = req.body;
12 | const user = await userModel.findOne({ email });
13 | if (!user) {
14 | return res.status(404).send('User Not Found');
15 | }
16 | // Compare hashed password
17 | const match = await bcrypt.compare(password, user.password);
18 | if (!match) {
19 | return res.status(401).send('Invalid Password');
20 | }
21 | const token = jwt.sign({id: user._id}, process.env.JWT_SECRET,{expiresIn:"1d"},);
22 | res.status(200).send({ message: "Login Success", success: true, token });
23 | } catch (error) {
24 | console.log(error);
25 | console.log(process.env.JWT_SECRET);
26 | res.status(500).send({ message: `Error in Login CTRL ${error.message}` });
27 | }
28 | };
29 |
30 | //Register Callback
31 | const registerController = async (req, res) => {
32 | try {
33 | const { name, email, password } = req.body;
34 |
35 | // Hash and salt password
36 | const saltRounds = 10;
37 | const hashedPassword = await bcrypt.hash(password, saltRounds);
38 |
39 | const newUser = new userModel({
40 | name,
41 | email,
42 | password: hashedPassword,
43 | });
44 | await newUser.save();
45 |
46 | res.status(201).json({
47 | success: true,
48 | newUser,
49 | });
50 | } catch (error) {
51 | res.status(400).json({
52 | success: false,
53 | error,
54 | });
55 | }
56 | };
57 |
58 | const authController = async (req, res) => {
59 | try {
60 | const user = await userModel.findById(req.body.userId);
61 | if (!user) {
62 | return res.status(200).send({
63 | message: "user not found",
64 | success: false,
65 | });
66 | } else {
67 | user.password = undefined; // move this line here
68 | res.status(200).send({
69 | success: true,
70 | data: user,
71 | });
72 | }
73 | } catch (error) {
74 | console.log(error);
75 | res.status(500).send({
76 | message: "auth error",
77 | success: false,
78 | error,
79 | });
80 | }
81 | };
82 |
83 | // Appply Doctor Controller
84 | const applyDoctorController = async (req, res) => {
85 | try {
86 | const newDoctor = await doctorModel({ ...req.body, status: "pending" });
87 | await newDoctor.save();
88 | const adminUser = await userModel.findOne({ isAdmin: true });
89 | const notification = adminUser.notification;
90 | notification.push({
91 | type: "apply-doctor-request",
92 | message: `${newDoctor.firstName} ${newDoctor.lastName} Has Applied For A Doctor Account`,
93 | data: {
94 | doctorId: newDoctor._id,
95 | name: newDoctor.firstName + " " + newDoctor.lastName,
96 | onClickPath: "/admin/doctors",
97 | },
98 | });
99 | await userModel.findByIdAndUpdate(adminUser._id, { notification });
100 | res.status(201).send({
101 | success: true,
102 | message: "Doctor Account Applied Successfully",
103 | });
104 | } catch (error) {
105 | console.log(error);
106 | res.status(500).send({
107 | success: false,
108 | error,
109 | message: "Error While Applying For Doctor",
110 | });
111 | }
112 | };
113 |
114 | // Notification controller
115 | const getAllNotificationController = async (req, res) => {
116 | try {
117 | const user = await userModel.findOne({ _id: req.body.userId });
118 | const seennotification = user.seennotification;
119 | const notification = user.notification;
120 | seennotification.push(...notification);
121 | user.notification = [];
122 | user.seennotification = notification;
123 | const updatedUser = await user.save();
124 | res.status(200).send({
125 | success: true,
126 | message: "All Notifications Marked As Read",
127 | data: updatedUser,
128 | });
129 | } catch (error) {
130 | console.log(error);
131 | res.status(500).send({
132 | message: "Error In Notification",
133 | success: false,
134 | error,
135 | });
136 | }
137 | };
138 |
139 | // delete notifications
140 | const deleteAllNotificationController = async (req, res) => {
141 | try {
142 | const user = await userModel.findOne({ _id: req.body.userId });
143 | user.notification = [];
144 | user.seennotification = [];
145 | const updatedUser = await user.save();
146 | updatedUser.password = undefined;
147 | res.status(200).send({
148 | success: true,
149 | message: "Notifications Deleted Successfully",
150 | data: updatedUser,
151 | });
152 | } catch (error) {
153 | console.log(error);
154 | res.status(500).send({
155 | success: false,
156 | message: "Unable To Delete All Notifications",
157 | error,
158 | });
159 | }
160 | };
161 |
162 | //GET ALL DOC
163 | const getAllDocotrsController = async (req, res) => {
164 | try {
165 | const doctors = await doctorModel.find({ status: "approved" });
166 | res.status(200).send({
167 | success: true,
168 | message: "Doctors Lists Fetched Successfully",
169 | data: doctors,
170 | });
171 | } catch (error) {
172 | console.log(error);
173 | res.status(500).send({
174 | success: false,
175 | error,
176 | message: "Error While Fetching Doctor",
177 | });
178 | }
179 | };
180 |
181 | // Checking Availability
182 | const bookingAvailabilityController = async (req, res) => {
183 | try {
184 | const date = moment(req.body.date, "DD-MM-YYYY").toISOString();
185 | const startTime = moment(req.body.time, "HH:mm").toISOString();
186 | const doctorId = req.body.doctorId;
187 | const doctor = await doctorModel.findById(doctorId);
188 | if (!doctor) {
189 | return res.status(404).send({
190 | message: "Doctor not found",
191 | success: false,
192 | });
193 | }
194 | const start = moment(doctor.starttime, "HH:mm").toISOString();
195 | const end = moment(doctor.endtime, "HH:mm").toISOString();
196 | if (!moment(startTime).isBetween(start, end, undefined, "[]")) {
197 | return res.status(200).send({
198 | message: "Appointment Not Available",
199 | success: false,
200 | });
201 | }
202 | const appointments = await appointmentModel.find({
203 | doctorId,
204 | date,
205 | time: startTime,
206 | });
207 | if (appointments.length > 0) {
208 | return res.status(200).send({
209 | message: "Appointment Not Available",
210 | success: false,
211 | });
212 | }
213 | return res.status(200).send({
214 | success: true,
215 | message: "Appointment Available",
216 | });
217 | } catch (error) {
218 | console.log(error);
219 | res.status(500).send({
220 | success: false,
221 | error,
222 | message: "Error Checking Appointment Availability",
223 | });
224 | }
225 | };
226 |
227 |
228 | //BOOK APPOINTMENT
229 | /*const bookAppointmentController = async (req, res) => {
230 | try {
231 | const date = moment(req.body.date, "DD-MM-YYYY").toISOString();
232 | const startTime = moment(req.body.time, "HH:mm").toISOString();
233 | const doctorId = req.body.doctorId;
234 | const doctor = await doctorModel.findById(doctorId);
235 | if (!doctor) {
236 | return res.status(404).send({
237 | message: "Doctor not found",
238 | success: false,
239 | });
240 | }
241 | const start = moment(doctor.starttime, "HH:mm").toISOString();
242 | const end = moment(doctor.endtime, "HH:mm").toISOString();
243 | if (!moment(startTime).isBetween(start, end, undefined, "[]")) {
244 | return res.status(400).send({
245 | message: "Selected time is not within doctor's available range",
246 | success: false,
247 | });
248 | }
249 | const appointments = await appointmentModel.find({
250 | doctorId,
251 | date,
252 | });
253 | if (appointments.length >= doctor.maxPatientsPerDay) {
254 | return res.status(400).send({
255 | message: "Maximum number of appointments reached for this day",
256 | success: false,
257 | });
258 | }
259 | const newAppointment = new appointmentModel({
260 | doctorId,
261 | userId: req.body.userId,
262 | date,
263 | time: startTime,
264 | doctorInfo: req.body.doctorInfo,
265 | userInfo: req.body.userInfo,
266 | });
267 | await newAppointment.save();
268 | return res.status(200).send({
269 | success: true,
270 | message: "Appointment Booked Successfully",
271 | });
272 | } catch (error) {
273 | console.log(error);
274 | res.status(500).send({
275 | success: false,
276 | error,
277 | message: "Error In Booking Appointment",
278 | });
279 | }
280 | };*/
281 |
282 | const bookAppointmentController = async (req, res) => {
283 | try {
284 | const date = moment(req.body.date, "DD-MM-YYYY").toISOString();
285 | const startTime = moment(req.body.time, "HH:mm").toISOString();
286 | const doctorId = req.body.doctorId;
287 | const doctor = await doctorModel.findById(doctorId);
288 | if (!doctor) {
289 | return res.status(404).send({
290 | message: "Doctor Not Found",
291 | success: false,
292 | });
293 | }
294 | const start = moment(doctor.starttime, "HH:mm").toISOString();
295 | const end = moment(doctor.endtime, "HH:mm").toISOString();
296 | if (!moment(startTime).isBetween(start, end, undefined, "[]")) {
297 | return res.status(400).send({
298 | message: "Selected Time Is Not Within Doctor's Available Range",
299 | success: false,
300 | });
301 | }
302 | const appointments = await appointmentModel.find({
303 | doctorId,
304 | date,
305 | status: "approved"
306 | });
307 | if (appointments.length >= doctor.maxPatientsPerDay) {
308 | return res.status(400).send({
309 | message: "Maximum Number Of Appointments Reached For This Day",
310 | success: false,
311 | });
312 | }
313 | const existingAppointment = await appointmentModel.findOne({
314 | doctorId,
315 | date,
316 | time: startTime,
317 | status: "approved"
318 | });
319 | if (existingAppointment) {
320 | return res.status(400).send({
321 | message: "Appointment Already Booked For This Time Slot",
322 | success: false,
323 | });
324 | }
325 | const newAppointment = new appointmentModel({
326 | doctorId,
327 | userId: req.body.userId,
328 | date,
329 | time: startTime,
330 | doctorInfo: req.body.doctorInfo,
331 | userInfo: req.body.userInfo,
332 | });
333 | await newAppointment.save();
334 | return res.status(200).send({
335 | success: true,
336 | message: "Appointment Booked Successfully",
337 | });
338 | } catch (error) {
339 | console.log(error);
340 | res.status(500).send({
341 | success: false,
342 | error,
343 | message: "Error In Booking Appointment",
344 | });
345 | }
346 | };
347 |
348 |
349 |
350 |
351 |
352 | const userAppointmentsController = async (req, res) => {
353 | try {
354 | const appointments = await appointmentModel.find({
355 | userId: req.body.userId,
356 | });
357 | res.status(200).send({
358 | success: true,
359 | message: "Users Appointments Fetch Successfully",
360 | data: appointments,
361 | });
362 | } catch (error) {
363 | console.log(error);
364 | res.status(500).send({
365 | success: false,
366 | error,
367 | message: "Error In User Appointments",
368 | });
369 | }
370 | };
371 |
372 | module.exports = { loginController, registerController, authController , applyDoctorController, getAllNotificationController, deleteAllNotificationController, getAllDocotrsController, bookAppointmentController, bookingAvailabilityController, userAppointmentsController};
--------------------------------------------------------------------------------