├── .gitignore
├── client
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── setupTests.js
│ ├── App.test.js
│ ├── components
│ │ ├── PublicRoute.js
│ │ ├── Doctor.js
│ │ ├── ProtectedRoute.js
│ │ ├── DoctorForm.js
│ │ └── Layout.js
│ ├── redux
│ │ ├── userSlice.js
│ │ ├── store.js
│ │ └── alertsSlice.js
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── pages
│ │ ├── Home.js
│ │ ├── ApplyDoctor.js
│ │ ├── Admin
│ │ │ ├── Userslist.js
│ │ │ └── DoctorsList.js
│ │ ├── Login.js
│ │ ├── Register.js
│ │ ├── Appointments.js
│ │ ├── Doctor
│ │ │ ├── Profile.js
│ │ │ └── DoctorAppointments.js
│ │ ├── Notifications.js
│ │ └── BookAppointment.js
│ ├── layout.css
│ ├── index.css
│ └── App.js
├── .gitignore
├── package.json
└── README.md
├── .env
├── config
└── dbConfig.js
├── middlewares
└── authMiddleware.js
├── models
├── userModel.js
├── appointmentModel.js
└── doctorModel.js
├── package.json
├── server.js
└── routes
├── adminRoute.js
├── doctorsRoute.js
└── userRoute.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | MONGO_URL = 'mongodb+srv://sathya:sathyapr@cluster0.wrqpt.mongodb.net/sheyhealthy-udemy'
2 | JWT_SECRET = 'SHEYHEALTHY'
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sathyaprakash195/sheyhealthy-udemy/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sathyaprakash195/sheyhealthy-udemy/HEAD/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sathyaprakash195/sheyhealthy-udemy/HEAD/client/public/logo512.png
--------------------------------------------------------------------------------
/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 | function PublicRoute(props) {
5 | if (localStorage.getItem("token")) {
6 | return ;
7 | } else {
8 | return props.children;
9 | }
10 | }
11 |
12 | export default PublicRoute;
13 |
--------------------------------------------------------------------------------
/client/src/redux/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 , reloadUserData } = userSlice.actions;
--------------------------------------------------------------------------------
/config/dbConfig.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | mongoose.connect(process.env.MONGO_URL);
4 |
5 | const connection = mongoose.connection;
6 |
7 | connection.on("connected", () => {
8 | console.log("MongoDB connection is successful");
9 | });
10 |
11 | connection.on("error", (error) => {
12 | console.log("Error in MongoDB connection", error);
13 | });
14 |
15 | module.exports = mongoose;
16 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { combineReducers } from "redux";
3 | import { alertsSlice } from "./alertsSlice";
4 | import { userSlice } from "./userSlice";
5 |
6 | const rootReducer = combineReducers({
7 | alerts: alertsSlice.reducer,
8 | user : userSlice.reducer,
9 | });
10 |
11 | const store = configureStore({
12 | reducer: rootReducer,
13 | });
14 | export default store;
15 |
--------------------------------------------------------------------------------
/client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/src/redux/alertsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const alertsSlice = createSlice({
4 | name: "alerts",
5 | initialState: {
6 | loading: false,
7 | },
8 | reducers: {
9 | showLoading: (state) => {
10 | state.loading = true;
11 | },
12 | hideLoading: (state) => {
13 | state.loading = false;
14 | },
15 | },
16 | });
17 |
18 | export const { showLoading, hideLoading } = alertsSlice.actions;
19 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import 'antd/dist/antd.min.css'
3 | import ReactDOM from "react-dom/client";
4 | import "./index.css";
5 | import { Provider } from "react-redux";
6 | import App from "./App";
7 | import reportWebVitals from "./reportWebVitals";
8 | import store from "./redux/store";
9 |
10 | const root = ReactDOM.createRoot(document.getElementById("root"));
11 | root.render(
12 |
13 |
14 |
15 | );
16 |
17 |
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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, decoded) => {
7 | if (err) {
8 | return res.status(401).send({
9 | message: "Auth failed",
10 | success: false,
11 | });
12 | } else {
13 | req.body.userId = decoded.id;
14 | next();
15 | }
16 | });
17 | } catch (error) {
18 | return res.status(401).send({
19 | message: "Auth failed",
20 | success: false,
21 | });
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | email: {
10 | type: String,
11 | required: true,
12 | },
13 | password: {
14 | type: String,
15 | required: true,
16 | },
17 | isDoctor: {
18 | type: Boolean,
19 | default: false,
20 | },
21 | isAdmin: {
22 | type: Boolean,
23 | default: false,
24 | },
25 | seenNotifications: {
26 | type: Array,
27 | default: [],
28 | },
29 | unseenNotifications: {
30 | type: Array,
31 | default: [],
32 | },
33 | },
34 | {
35 | timestamps: true,
36 | }
37 | );
38 |
39 | const userModel = mongoose.model("users", userSchema);
40 |
41 | module.exports = userModel;
42 |
--------------------------------------------------------------------------------
/models/appointmentModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const appointmentSchema = new mongoose.Schema(
3 | {
4 | userId: {
5 | type: String,
6 | required: true,
7 | },
8 | doctorId: {
9 | type: String,
10 | required: true,
11 | },
12 | doctorInfo: {
13 | type: Object,
14 | required: true,
15 | },
16 | userInfo: {
17 | type: Object,
18 | required: true,
19 | },
20 | date: {
21 | type: String,
22 | required: true,
23 | },
24 | time: {
25 | type: String,
26 | required: true,
27 | },
28 | status: {
29 | type: String,
30 | required: true,
31 | default: "pending",
32 | },
33 | },
34 | {
35 | timestamps: true,
36 | }
37 | );
38 |
39 | const appointmentModel = mongoose.model("appointmenst", appointmentSchema);
40 | module.exports = appointmentModel;
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sheyhealthy",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "engines": {
7 | "node": "15.7.0",
8 | "npm": "7.4.3"
9 | },
10 | "scripts": {
11 | "client-install": "npm install --prefix client",
12 | "server": "nodemon server.js",
13 | "client": "npm start --prefix client",
14 | "dev": "concurrently \"npm run server\" \"npm run client\"",
15 | "start": "node server.js",
16 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
17 | },
18 | "author": "",
19 | "license": "ISC",
20 | "dependencies": {
21 | "bcrypt": "^5.0.1",
22 | "bcryptjs": "^2.4.3",
23 | "dotenv": "^16.0.1",
24 | "express": "^4.18.1",
25 | "jsonwebtoken": "^8.5.1",
26 | "moment": "^2.29.3",
27 | "mongoose": "^6.4.0",
28 | "nodemon": "^2.0.16"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 | require("dotenv").config();
4 | const dbConfig = require("./config/dbConfig");
5 | app.use(express.json());
6 | const userRoute = require("./routes/userRoute");
7 | const adminRoute = require("./routes/adminRoute");
8 | const doctorRoute = require("./routes/doctorsRoute");
9 | const path = require("path");
10 |
11 | app.use("/api/user", userRoute);
12 | app.use("/api/admin", adminRoute);
13 | app.use("/api/doctor", doctorRoute);
14 |
15 | if (process.env.NODE_ENV === "production") {
16 | app.use("/", express.static("client/build"));
17 |
18 | app.get("*", (req, res) => {
19 | res.sendFile(path.resolve(__dirname, "client/build/index.html"));
20 | });
21 | }
22 | const port = process.env.PORT || 5000;
23 |
24 | app.get("/", (req, res) => res.send("Hello World!"));
25 | app.listen(port, () => console.log(`Node Express Server Started at ${port}!`));
26 |
--------------------------------------------------------------------------------
/client/src/components/Doctor.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | function Doctor({ doctor }) {
5 | const navigate = useNavigate();
6 | return (
7 |
navigate(`/book-appointment/${doctor._id}`)}
10 | >
11 |
12 | {doctor.firstName} {doctor.lastName}
13 |
14 |
15 |
16 | Phone Number :
17 | {doctor.phoneNumber}
18 |
19 |
20 | Address :
21 | {doctor.address}
22 |
23 |
24 | Fee per Visit :
25 | {doctor.feePerCunsultation}
26 |
27 |
28 | Timings :
29 | {doctor.timings[0]} - {doctor.timings[1]}
30 |
31 |
32 | );
33 | }
34 |
35 | export default Doctor;
36 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
22 |
23 | Sheyhealthy-Dev
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/models/doctorModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const doctorSchema = new mongoose.Schema(
3 | {
4 | userId: {
5 | type: String,
6 | required: true,
7 | },
8 | firstName: {
9 | type: String,
10 | required: true,
11 | },
12 | lastName: {
13 | type: String,
14 | required: true,
15 | },
16 | phoneNumber: {
17 | type: String,
18 | required: true,
19 | },
20 | website: {
21 | type: String,
22 | required: true,
23 | },
24 | address: {
25 | type: String,
26 | required: true,
27 | },
28 | specialization: {
29 | type: String,
30 | required: true,
31 | },
32 | experience: {
33 | type: String,
34 | required: true,
35 | },
36 | feePerCunsultation: {
37 | type: Number,
38 | required: true,
39 | },
40 | timings : {
41 | type: Array,
42 | required: true,
43 | },
44 | status: {
45 | type: String,
46 | default: "pending",
47 | }
48 | },
49 | {
50 | timestamps: true,
51 | }
52 | );
53 |
54 | const doctorModel = mongoose.model("doctors", doctorSchema);
55 | module.exports = doctorModel;
56 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.8.2",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^13.3.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "antd": "^4.20.2",
11 | "axios": "^0.27.2",
12 | "moment": "^2.29.3",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-hot-toast": "^2.2.0",
16 | "react-redux": "^8.0.2",
17 | "react-router-dom": "^6.3.0",
18 | "react-scripts": "5.0.1",
19 | "redux": "^4.2.0",
20 | "redux-toolkit": "^1.1.2",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | },
47 | "proxy": "http://localhost:5000"
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import Layout from "../components/Layout";
4 | import { Col, Row } from "antd";
5 | import Doctor from "../components/Doctor";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { showLoading, hideLoading } from "../redux/alertsSlice";
8 | function Home() {
9 | const [doctors, setDoctors] = useState([]);
10 | const dispatch = useDispatch();
11 | const getData = async () => {
12 | try {
13 | dispatch(showLoading())
14 | const response = await axios.get("/api/user/get-all-approved-doctors", {
15 | headers: {
16 | Authorization: "Bearer " + localStorage.getItem("token"),
17 | },
18 | });
19 | dispatch(hideLoading())
20 | if (response.data.success) {
21 | setDoctors(response.data.data);
22 | }
23 | } catch (error) {
24 | dispatch(hideLoading())
25 | }
26 | };
27 |
28 | useEffect(() => {
29 | getData();
30 | }, []);
31 | return (
32 |
33 |
34 | {doctors.map((doctor) => (
35 |
36 |
37 |
38 | ))}
39 |
40 |
41 | );
42 | }
43 |
44 | export default Home;
45 |
--------------------------------------------------------------------------------
/client/src/components/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate, useNavigate } from "react-router-dom";
3 | import { useEffect } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import axios from "axios";
6 | import { setUser } from "../redux/userSlice";
7 | import { showLoading, hideLoading } from "../redux/alertsSlice";
8 |
9 | function ProtectedRoute(props) {
10 | const { user } = useSelector((state) => state.user);
11 | const dispatch = useDispatch();
12 | const navigate = useNavigate();
13 | const getUser = async () => {
14 | try {
15 | dispatch(showLoading())
16 | const response = await axios.post(
17 | "/api/user/get-user-info-by-id",
18 | { token: localStorage.getItem("token") },
19 | {
20 | headers: {
21 | Authorization: `Bearer ${localStorage.getItem("token")}`,
22 | },
23 | }
24 | );
25 | dispatch(hideLoading());
26 | if (response.data.success) {
27 | dispatch(setUser(response.data.data));
28 | } else {
29 | localStorage.clear()
30 | navigate("/login");
31 | }
32 | } catch (error) {
33 | dispatch(hideLoading());
34 | localStorage.clear()
35 | navigate("/login");
36 | }
37 | };
38 |
39 | useEffect(() => {
40 | if (!user) {
41 | getUser();
42 | }
43 | }, [user]);
44 |
45 | if (localStorage.getItem("token")) {
46 | return props.children;
47 | } else {
48 | return ;
49 | }
50 | }
51 |
52 | export default ProtectedRoute;
53 |
--------------------------------------------------------------------------------
/client/src/pages/ApplyDoctor.js:
--------------------------------------------------------------------------------
1 | import { Button, Col, Form, Input, Row, TimePicker } from "antd";
2 | import React from "react";
3 | import Layout from "../components/Layout";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { showLoading, hideLoading } from "../redux/alertsSlice";
6 | import { toast } from "react-hot-toast";
7 | import axios from "axios";
8 | import { useNavigate } from "react-router-dom";
9 | import DoctorForm from "../components/DoctorForm";
10 | import moment from "moment";
11 |
12 | function ApplyDoctor() {
13 | const dispatch = useDispatch();
14 | const { user } = useSelector((state) => state.user);
15 | const navigate = useNavigate();
16 | const onFinish = async (values) => {
17 | try {
18 | dispatch(showLoading());
19 | const response = await axios.post(
20 | "/api/user/apply-doctor-account",
21 | {
22 | ...values,
23 | userId: user._id,
24 | timings: [
25 | moment(values.timings[0]).format("HH:mm"),
26 | moment(values.timings[1]).format("HH:mm"),
27 | ],
28 | },
29 | {
30 | headers: {
31 | Authorization: `Bearer ${localStorage.getItem("token")}`,
32 | },
33 | }
34 | );
35 | dispatch(hideLoading());
36 | if (response.data.success) {
37 | toast.success(response.data.message);
38 | navigate("/");
39 | } else {
40 | toast.error(response.data.message);
41 | }
42 | } catch (error) {
43 | dispatch(hideLoading());
44 | toast.error("Something went wrong");
45 | }
46 | };
47 |
48 | return (
49 |
50 | Apply Doctor
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | export default ApplyDoctor;
59 |
--------------------------------------------------------------------------------
/client/src/pages/Admin/Userslist.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import Layout from "../../components/Layout";
4 | import { showLoading, hideLoading } from "../../redux/alertsSlice";
5 | import axios from "axios";
6 | import { Table } from "antd";
7 | import moment from "moment";
8 |
9 | function Userslist() {
10 | const [users, setUsers] = useState([]);
11 | const dispatch = useDispatch();
12 | const getUsersData = async () => {
13 | try {
14 | dispatch(showLoading());
15 | const resposne = await axios.get("/api/admin/get-all-users", {
16 | headers: {
17 | Authorization: `Bearer ${localStorage.getItem("token")}`,
18 | },
19 | });
20 | dispatch(hideLoading());
21 | if (resposne.data.success) {
22 | setUsers(resposne.data.data);
23 | }
24 | } catch (error) {
25 | dispatch(hideLoading());
26 | }
27 | };
28 |
29 | useEffect(() => {
30 | getUsersData();
31 | }, []);
32 |
33 | const columns = [
34 | {
35 | title: "Name",
36 | dataIndex: "name",
37 | },
38 | {
39 | title: "Email",
40 | dataIndex: "email",
41 | },
42 | {
43 | title: "Created At",
44 | dataIndex: "createdAt",
45 | render: (record , text) => moment(record.createdAt).format("DD-MM-YYYY"),
46 | },
47 | {
48 | title: "Actions",
49 | dataIndex: "actions",
50 | render: (text, record) => (
51 |
52 |
Block
53 |
54 | ),
55 | },
56 | ];
57 |
58 | return (
59 |
60 | Users List
61 |
62 |
63 |
64 | );
65 | }
66 |
67 | export default Userslist;
68 |
--------------------------------------------------------------------------------
/client/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import { Button, Form, Input } from "antd";
2 | import React from "react";
3 | import toast from "react-hot-toast";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { Link, useNavigate } from "react-router-dom";
6 | import axios from "axios";
7 | import { hideLoading, showLoading } from "../redux/alertsSlice";
8 |
9 | function Login() {
10 | const dispatch = useDispatch();
11 | const navigate = useNavigate();
12 | const onFinish = async (values) => {
13 | try {
14 | dispatch(showLoading());
15 | const response = await axios.post("/api/user/login", values);
16 | dispatch(hideLoading());
17 | if (response.data.success) {
18 | toast.success(response.data.message);
19 | localStorage.setItem("token", response.data.data);
20 | navigate("/");
21 | } else {
22 | toast.error(response.data.message);
23 | }
24 | } catch (error) {
25 | dispatch(hideLoading());
26 | toast.error("Something went wrong");
27 | }
28 | };
29 |
30 | return (
31 |
54 | );
55 | }
56 |
57 | export default Login;
58 |
--------------------------------------------------------------------------------
/client/src/layout.css:
--------------------------------------------------------------------------------
1 | .main{
2 | padding: 20px;
3 | }
4 |
5 | .logo{
6 | color: white;
7 | font-weight: bold;
8 | }
9 | .role{
10 | color: white;
11 | font-size: 14px;
12 | margin-top:-20px ;
13 | }
14 | .sidebar{
15 | background-color: #005555;
16 | border-radius: 5px;
17 | box-shadow: 0 0 2px gray;
18 | margin-right: 20px;
19 | min-height: 100%;
20 | padding: 10px;
21 | }
22 | .content{
23 | width: 100%;
24 | height: 100%;
25 | }
26 | .header{
27 | background-color: white;
28 | border-radius: 5px;
29 | box-shadow: 0 0 2px gray;
30 | margin-bottom: 20px;
31 | height: 10vh;
32 | display: flex;
33 | align-items: center;
34 | justify-content: space-between;
35 | }
36 |
37 | .body{
38 | background-color: white;
39 | border-radius: 5px;
40 | box-shadow: 0 0 2px gray;
41 | height: 82vh;
42 | width: 100%;
43 | padding: 15px;
44 | overflow-x: scroll !important;
45 | }
46 |
47 |
48 | .menu{
49 | margin-top: 100px;
50 | padding: 0 10px;
51 | }
52 |
53 | .menu-item{
54 | margin-top: 30px;
55 | }
56 |
57 | .menu-item a{
58 | color: rgba(255, 255, 255, 0.727);
59 | text-decoration: none;
60 | font-size: 18px;
61 | padding: 0 10px;
62 | }
63 | .menu-item i{
64 | color: rgba(255, 255, 255, 0.716);
65 | text-decoration: none;
66 | font-size: 18px;
67 | margin:0 15px;
68 | }
69 |
70 | .active-menu-item{
71 | color: white;
72 | background-color: #013737;
73 | padding: 5px;
74 | border-radius: 5px;
75 | }
76 |
77 | .header-action-icon{
78 | font-size: 30px;
79 | color: black !important;
80 | cursor: pointer;
81 | padding-left: 10px;
82 | }
83 |
84 | .ant-scroll-number-only-unit{
85 | padding-right: -10px !important;
86 | padding-bottom: -10px !important;
87 | }
88 |
89 | @media screen and (max-width: 768px){
90 |
91 | .menu{
92 | padding: 0 5px;
93 | }
94 |
95 | }
--------------------------------------------------------------------------------
/client/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import { Button, Form, Input } from "antd";
2 | import React from "react";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { useDispatch } from "react-redux";
5 | import axios from "axios";
6 | import toast from "react-hot-toast";
7 | import { hideLoading, showLoading } from "../redux/alertsSlice";
8 |
9 | function Register() {
10 | const dispatch = useDispatch();
11 | const navigate = useNavigate();
12 | const onFinish = async (values) => {
13 | try {
14 | dispatch(showLoading());
15 | const response = await axios.post("/api/user/register", values);
16 | dispatch(hideLoading());
17 | if (response.data.success) {
18 | toast.success(response.data.message);
19 | navigate("/login");
20 | } else {
21 | toast.error(response.data.message);
22 | }
23 | } catch (error) {
24 | dispatch(hideLoading());
25 | toast.error("Something went wrong");
26 | }
27 | };
28 |
29 | return (
30 |
57 | );
58 | }
59 |
60 | export default Register;
61 |
--------------------------------------------------------------------------------
/client/src/pages/Appointments.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import Layout from "../components/Layout";
4 | import { showLoading, hideLoading } from "../redux/alertsSlice";
5 | import { toast } from "react-hot-toast";
6 | import axios from "axios";
7 | import { Table } from "antd";
8 | import moment from "moment";
9 |
10 | function Appointments() {
11 | const [appointments, setAppointments] = useState([]);
12 | const dispatch = useDispatch();
13 | const getAppointmentsData = async () => {
14 | try {
15 | dispatch(showLoading());
16 | const resposne = await axios.get("/api/user/get-appointments-by-user-id", {
17 | headers: {
18 | Authorization: `Bearer ${localStorage.getItem("token")}`,
19 | },
20 | });
21 | dispatch(hideLoading());
22 | if (resposne.data.success) {
23 | setAppointments(resposne.data.data);
24 | }
25 | } catch (error) {
26 | dispatch(hideLoading());
27 | }
28 | };
29 | const columns = [
30 | {
31 | title: "Id",
32 | dataIndex: "_id",
33 | },
34 | {
35 | title: "Doctor",
36 | dataIndex: "name",
37 | render: (text, record) => (
38 |
39 | {record.doctorInfo.firstName} {record.doctorInfo.lastName}
40 |
41 | ),
42 | },
43 | {
44 | title: "Phone",
45 | dataIndex: "phoneNumber",
46 | render: (text, record) => (
47 |
48 | {record.doctorInfo.phoneNumber}
49 |
50 | ),
51 | },
52 | {
53 | title: "Date & Time",
54 | dataIndex: "createdAt",
55 | render: (text, record) => (
56 |
57 | {moment(record.date).format("DD-MM-YYYY")} {moment(record.time).format("HH:mm")}
58 |
59 | ),
60 | },
61 | {
62 | title: "Status",
63 | dataIndex: "status",
64 | }
65 | ];
66 | useEffect(() => {
67 | getAppointmentsData();
68 | }, []);
69 | return
70 | Appointments
71 |
72 |
73 |
74 | }
75 |
76 | export default Appointments;
77 |
--------------------------------------------------------------------------------
/routes/adminRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const User = require("../models/userModel");
4 | const Doctor = require("../models/doctorModel");
5 | const authMiddleware = require("../middlewares/authMiddleware");
6 |
7 | router.get("/get-all-doctors", authMiddleware, async (req, res) => {
8 | try {
9 | const doctors = await Doctor.find({});
10 | res.status(200).send({
11 | message: "Doctors fetched successfully",
12 | success: true,
13 | data: doctors,
14 | });
15 | } catch (error) {
16 | console.log(error);
17 | res.status(500).send({
18 | message: "Error applying doctor account",
19 | success: false,
20 | error,
21 | });
22 | }
23 | });
24 |
25 | router.get("/get-all-users", authMiddleware, async (req, res) => {
26 | try {
27 | const users = await User.find({});
28 | res.status(200).send({
29 | message: "Users fetched successfully",
30 | success: true,
31 | data: users,
32 | });
33 | } catch (error) {
34 | console.log(error);
35 | res.status(500).send({
36 | message: "Error applying doctor account",
37 | success: false,
38 | error,
39 | });
40 | }
41 | });
42 |
43 | router.post(
44 | "/change-doctor-account-status",
45 | authMiddleware,
46 | async (req, res) => {
47 | try {
48 | const { doctorId, status } = req.body;
49 | const doctor = await Doctor.findByIdAndUpdate(doctorId, {
50 | status,
51 | });
52 |
53 | const user = await User.findOne({ _id: doctor.userId });
54 | const unseenNotifications = user.unseenNotifications;
55 | unseenNotifications.push({
56 | type: "new-doctor-request-changed",
57 | message: `Your doctor account has been ${status}`,
58 | onClickPath: "/notifications",
59 | });
60 | user.isDoctor = status === "approved" ? true : false;
61 | await user.save();
62 |
63 | res.status(200).send({
64 | message: "Doctor status updated successfully",
65 | success: true,
66 | data: doctor,
67 | });
68 | } catch (error) {
69 | console.log(error);
70 | res.status(500).send({
71 | message: "Error applying doctor account",
72 | success: false,
73 | error,
74 | });
75 | }
76 | }
77 | );
78 |
79 |
80 |
81 | module.exports = router;
82 |
--------------------------------------------------------------------------------
/client/src/pages/Doctor/Profile.js:
--------------------------------------------------------------------------------
1 | import { Button, Col, Form, Input, Row, TimePicker } from "antd";
2 | import React, { useEffect, useState } from "react";
3 | import Layout from "../../components/Layout";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { showLoading, hideLoading } from "../../redux/alertsSlice";
6 | import { toast } from "react-hot-toast";
7 | import axios from "axios";
8 | import { useNavigate, useParams } from "react-router-dom";
9 | import DoctorForm from "../../components/DoctorForm";
10 | import moment from "moment";
11 |
12 | function Profile() {
13 | const { user } = useSelector((state) => state.user);
14 | const params = useParams();
15 | const [doctor, setDoctor] = useState(null);
16 | const dispatch = useDispatch();
17 | const navigate = useNavigate();
18 | const onFinish = async (values) => {
19 | try {
20 | dispatch(showLoading());
21 | const response = await axios.post(
22 | "/api/doctor/update-doctor-profile",
23 | {
24 | ...values,
25 | userId: user._id,
26 | timings: [
27 | moment(values.timings[0]).format("HH:mm"),
28 | moment(values.timings[1]).format("HH:mm"),
29 | ],
30 | },
31 | {
32 | headers: {
33 | Authorization: `Bearer ${localStorage.getItem("token")}`,
34 | },
35 | }
36 | );
37 | dispatch(hideLoading());
38 | if (response.data.success) {
39 | toast.success(response.data.message);
40 | navigate("/");
41 | } else {
42 | toast.error(response.data.message);
43 | }
44 | } catch (error) {
45 | dispatch(hideLoading());
46 | toast.error("Something went wrong");
47 | }
48 | };
49 |
50 | const getDoctorData = async () => {
51 | try {
52 | dispatch(showLoading());
53 | const response = await axios.post(
54 | "/api/doctor/get-doctor-info-by-user-id",
55 | {
56 | userId: params.userId,
57 | },
58 | {
59 | headers: {
60 | Authorization: `Bearer ${localStorage.getItem("token")}`,
61 | },
62 | }
63 | );
64 |
65 | dispatch(hideLoading());
66 | if (response.data.success) {
67 | setDoctor(response.data.data);
68 | }
69 | } catch (error) {
70 | console.log(error);
71 | dispatch(hideLoading());
72 | }
73 | };
74 |
75 | useEffect(() => {
76 | getDoctorData();
77 | }, []);
78 | return (
79 |
80 | Doctor Profile
81 |
82 | {doctor && }
83 |
84 | );
85 | }
86 |
87 | export default Profile;
88 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap');
2 |
3 | *{
4 | font-family: 'Montserrat', sans-serif !important;
5 | }
6 |
7 | /* antd override */
8 | input{
9 | border-radius: 2px !important;
10 | border: 1px solid rgba(128, 128, 128, 0.521) !important;
11 | height: 40px !important;
12 | }
13 | input:focus , .ant-picker{
14 | outline: none !important;
15 | box-shadow: none !important;
16 | border: 1px solid gray !important;
17 | }
18 | label{
19 | font-size: 16px !important;
20 | color: black !important;
21 | }
22 | .ant-form-item{
23 | margin: 10px 0 !important;;
24 | }
25 | .ant-picker-input input{
26 | border: none !important;
27 | }
28 | .ant-picker{
29 | border-radius: 2px !important;
30 | border: 1px solid rgba(128, 128, 128, 0.521) !important;
31 | height: 40px !important;
32 | }
33 | /* Common styling */
34 |
35 | .spinner-parent{
36 | display: flex;
37 | justify-content: center;
38 | align-items: center;
39 | background-color: rgba(0, 0, 0, 0.704);
40 | position: fixed;
41 | top: 0;
42 | left: 0;
43 | width: 100%;
44 | height: 100%;
45 | z-index: 9999;
46 | }
47 | .spinner-border{
48 | width: 5rem;
49 | height: 5rem;
50 | color: white !important;
51 | }
52 |
53 | .page-title{
54 | font-size: 25px;
55 | color: rgba(0, 0, 0, 0.833);
56 | font-weight: bold;
57 | }
58 | .card-title{
59 | font-size: 22px;
60 | font-weight: bold;
61 | color: rgba(0, 0, 0, 0.627);
62 |
63 | }
64 | .normal-text{
65 | font-size: 1rem;
66 | }
67 | .card{
68 | box-shadow: 0 0 2px rgb(189, 188, 188);
69 | border-radius: 0 !important;
70 | }
71 | .primary-button{
72 | background-color: #005555 !important;
73 | border-color: #005555 !important;
74 | height: 40px !important;
75 | width: 100% !important;
76 | color: white !important;
77 | font-size: 16px !important;
78 | width: max-content !important;
79 | }
80 | .full-width-button{
81 | width: 100% !important;
82 | }
83 | p{
84 | color: rgba(0, 0, 0, 0.673) !important;
85 | padding: 0 !important;
86 | margin: 0 !important;
87 | }
88 |
89 | .anchor{
90 | color: black !important;
91 | text-decoration: underline;
92 | cursor: pointer;
93 | font-size: 16px !important;
94 | }
95 | .anchor:hover{
96 | text-decoration: underline !important;
97 | }
98 |
99 | .cursor-pointer{
100 | cursor: pointer;
101 | }
102 |
103 |
104 | /* authentication pages */
105 |
106 | .authentication{
107 | height: 100vh;
108 | display: flex;
109 | align-items: center;
110 | justify-content: center;
111 | background-color: #005555;
112 | }
113 | .authentication-form{
114 | width: 400px;
115 | }
116 |
117 | .authentication .card-title{
118 | font-size: 1.5rem;
119 | font-weight: bold;
120 | background-color: orangered;
121 | color: white !important;
122 | max-width: max-content !important;
123 | padding: 7px 15px;
124 | margin-left: -40px;
125 | border-bottom-left-radius: 10px;
126 | }
--------------------------------------------------------------------------------
/client/src/pages/Notifications.js:
--------------------------------------------------------------------------------
1 | import { Tabs } from "antd";
2 | import axios from "axios";
3 | import React from "react";
4 | import toast from "react-hot-toast";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { useNavigate } from "react-router-dom";
7 | import Layout from "../components/Layout";
8 | import { hideLoading, showLoading } from "../redux/alertsSlice";
9 | import { setUser } from "../redux/userSlice";
10 |
11 | function Notifications() {
12 | const {user} = useSelector((state) => state.user);
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const markAllAsSeen=async()=>{
16 | try {
17 | dispatch(showLoading());
18 | const response = await axios.post("/api/user/mark-all-notifications-as-seen", {userId : user._id} , {
19 | headers: {
20 | Authorization : `Bearer ${localStorage.getItem("token")}`
21 | }
22 | });
23 | dispatch(hideLoading());
24 | if (response.data.success) {
25 | toast.success(response.data.message)
26 | dispatch(setUser(response.data.data));
27 | } else {
28 | toast.error(response.data.message);
29 | }
30 | } catch (error) {
31 | dispatch(hideLoading());
32 | toast.error("Something went wrong");
33 | }
34 | }
35 |
36 | const deleteAll=async()=>{
37 | try {
38 | dispatch(showLoading());
39 | const response = await axios.post("/api/user/delete-all-notifications", {userId : user._id} , {
40 | headers: {
41 | Authorization : `Bearer ${localStorage.getItem("token")}`
42 | }
43 | });
44 | dispatch(hideLoading());
45 | if (response.data.success) {
46 | toast.success(response.data.message)
47 | dispatch(setUser(response.data.data));
48 | } else {
49 | toast.error(response.data.message);
50 | }
51 | } catch (error) {
52 | dispatch(hideLoading());
53 | toast.error("Something went wrong");
54 | }
55 | }
56 | return (
57 |
58 | Notifications
59 |
60 |
61 |
62 |
63 |
64 |
markAllAsSeen()}>Mark all as seen
65 |
66 |
67 | {user?.unseenNotifications.map((notification) => (
68 | navigate(notification.onClickPath)}>
69 |
{notification.message}
70 |
71 | ))}
72 |
73 |
74 |
75 |
deleteAll()}>Delete all
76 |
77 | {user?.seenNotifications.map((notification) => (
78 | navigate(notification.onClickPath)}>
79 |
{notification.message}
80 |
81 | ))}
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | export default Notifications;
89 |
--------------------------------------------------------------------------------
/client/src/pages/Admin/DoctorsList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import Layout from "../../components/Layout";
4 | import { showLoading, hideLoading } from "../../redux/alertsSlice";
5 | import {toast} from 'react-hot-toast'
6 | import axios from "axios";
7 | import { Table } from "antd";
8 | import moment from "moment";
9 |
10 | function DoctorsList() {
11 | const [doctors, setDoctors] = useState([]);
12 | const dispatch = useDispatch();
13 | const getDoctorsData = async () => {
14 | try {
15 | dispatch(showLoading());
16 | const resposne = await axios.get("/api/admin/get-all-doctors", {
17 | headers: {
18 | Authorization: `Bearer ${localStorage.getItem("token")}`,
19 | },
20 | });
21 | dispatch(hideLoading());
22 | if (resposne.data.success) {
23 | setDoctors(resposne.data.data);
24 | }
25 | } catch (error) {
26 | dispatch(hideLoading());
27 | }
28 | };
29 |
30 | const changeDoctorStatus = async (record, status) => {
31 | try {
32 | dispatch(showLoading());
33 | const resposne = await axios.post(
34 | "/api/admin/change-doctor-account-status",
35 | { doctorId: record._id, userId: record.userId, status: status },
36 | {
37 | headers: {
38 | Authorization: `Bearer ${localStorage.getItem("token")}`,
39 | },
40 | }
41 | );
42 | dispatch(hideLoading());
43 | if (resposne.data.success) {
44 | toast.success(resposne.data.message);
45 | getDoctorsData();
46 | }
47 | } catch (error) {
48 | toast.error('Error changing doctor account status');
49 | dispatch(hideLoading());
50 | }
51 | };
52 | useEffect(() => {
53 | getDoctorsData();
54 | }, []);
55 | const columns = [
56 | {
57 | title: "Name",
58 | dataIndex: "name",
59 | render: (text, record) => (
60 |
61 | {record.firstName} {record.lastName}
62 |
63 | ),
64 | },
65 | {
66 | title: "Phone",
67 | dataIndex: "phoneNumber",
68 | },
69 | {
70 | title: "Created At",
71 | dataIndex: "createdAt",
72 | render: (record , text) => moment(record.createdAt).format("DD-MM-YYYY"),
73 | },
74 | {
75 | title: "status",
76 | dataIndex: "status",
77 | },
78 | {
79 | title: "Actions",
80 | dataIndex: "actions",
81 | render: (text, record) => (
82 |
83 | {record.status === "pending" && (
84 |
changeDoctorStatus(record, "approved")}
87 | >
88 | Approve
89 |
90 | )}
91 | {record.status === "approved" && (
92 | changeDoctorStatus(record, "blocked")}
95 | >
96 | Block
97 |
98 | )}
99 |
100 | ),
101 | },
102 | ];
103 | return (
104 |
105 | Doctors List
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | export default DoctorsList;
113 |
--------------------------------------------------------------------------------
/routes/doctorsRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const Doctor = require("../models/doctorModel");
4 | const authMiddleware = require("../middlewares/authMiddleware");
5 | const Appointment = require("../models/appointmentModel");
6 | const User = require("../models/userModel");
7 |
8 | router.post("/get-doctor-info-by-user-id", authMiddleware, async (req, res) => {
9 | try {
10 | const doctor = await Doctor.findOne({ userId: req.body.userId });
11 | res.status(200).send({
12 | success: true,
13 | message: "Doctor info fetched successfully",
14 | data: doctor,
15 | });
16 | } catch (error) {
17 | res
18 | .status(500)
19 | .send({ message: "Error getting doctor info", success: false, error });
20 | }
21 | });
22 |
23 | router.post("/get-doctor-info-by-id", authMiddleware, async (req, res) => {
24 | try {
25 | const doctor = await Doctor.findOne({ _id: req.body.doctorId });
26 | res.status(200).send({
27 | success: true,
28 | message: "Doctor info fetched successfully",
29 | data: doctor,
30 | });
31 | } catch (error) {
32 | res
33 | .status(500)
34 | .send({ message: "Error getting doctor info", success: false, error });
35 | }
36 | });
37 |
38 | router.post("/update-doctor-profile", authMiddleware, async (req, res) => {
39 | try {
40 | const doctor = await Doctor.findOneAndUpdate(
41 | { userId: req.body.userId },
42 | req.body
43 | );
44 | res.status(200).send({
45 | success: true,
46 | message: "Doctor profile updated successfully",
47 | data: doctor,
48 | });
49 | } catch (error) {
50 | res
51 | .status(500)
52 | .send({ message: "Error getting doctor info", success: false, error });
53 | }
54 | });
55 |
56 | router.get(
57 | "/get-appointments-by-doctor-id",
58 | authMiddleware,
59 | async (req, res) => {
60 | try {
61 | const doctor = await Doctor.findOne({ userId: req.body.userId });
62 | const appointments = await Appointment.find({ doctorId: doctor._id });
63 | res.status(200).send({
64 | message: "Appointments fetched successfully",
65 | success: true,
66 | data: appointments,
67 | });
68 | } catch (error) {
69 | console.log(error);
70 | res.status(500).send({
71 | message: "Error fetching appointments",
72 | success: false,
73 | error,
74 | });
75 | }
76 | }
77 | );
78 |
79 | router.post("/change-appointment-status", authMiddleware, async (req, res) => {
80 | try {
81 | const { appointmentId, status } = req.body;
82 | const appointment = await Appointment.findByIdAndUpdate(appointmentId, {
83 | status,
84 | });
85 |
86 | const user = await User.findOne({ _id: appointment.userId });
87 | const unseenNotifications = user.unseenNotifications;
88 | unseenNotifications.push({
89 | type: "appointment-status-changed",
90 | message: `Your appointment status has been ${status}`,
91 | onClickPath: "/appointments",
92 | });
93 |
94 | await user.save();
95 |
96 | res.status(200).send({
97 | message: "Appointment status updated successfully",
98 | success: true
99 | });
100 | } catch (error) {
101 | console.log(error);
102 | res.status(500).send({
103 | message: "Error changing appointment status",
104 | success: false,
105 | error,
106 | });
107 | }
108 | });
109 |
110 | module.exports = router;
111 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/src/pages/Doctor/DoctorAppointments.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import Layout from "../../components/Layout";
4 | import { showLoading, hideLoading } from "../../redux/alertsSlice";
5 | import { toast } from "react-hot-toast";
6 | import axios from "axios";
7 | import { Table } from "antd";
8 | import moment from "moment";
9 |
10 | function DoctorAppointments() {
11 | const [appointments, setAppointments] = useState([]);
12 | const dispatch = useDispatch();
13 | const getAppointmentsData = async () => {
14 | try {
15 | dispatch(showLoading());
16 | const resposne = await axios.get(
17 | "/api/doctor/get-appointments-by-doctor-id",
18 | {
19 | headers: {
20 | Authorization: `Bearer ${localStorage.getItem("token")}`,
21 | },
22 | }
23 | );
24 | dispatch(hideLoading());
25 | if (resposne.data.success) {
26 | setAppointments(resposne.data.data);
27 | }
28 | } catch (error) {
29 | dispatch(hideLoading());
30 | }
31 | };
32 |
33 | const changeAppointmentStatus = async (record, status) => {
34 | try {
35 | dispatch(showLoading());
36 | const resposne = await axios.post(
37 | "/api/doctor/change-appointment-status",
38 | { appointmentId : record._id, status: status },
39 | {
40 | headers: {
41 | Authorization: `Bearer ${localStorage.getItem("token")}`,
42 | },
43 | }
44 | );
45 | dispatch(hideLoading());
46 | if (resposne.data.success) {
47 | toast.success(resposne.data.message);
48 | getAppointmentsData();
49 | }
50 | } catch (error) {
51 | toast.error("Error changing doctor account status");
52 | dispatch(hideLoading());
53 | }
54 | };
55 | const columns = [
56 | {
57 | title: "Id",
58 | dataIndex: "_id",
59 | },
60 | {
61 | title: "Patient",
62 | dataIndex: "name",
63 | render: (text, record) => {record.userInfo.name},
64 | },
65 | {
66 | title: "Phone",
67 | dataIndex: "phoneNumber",
68 | render: (text, record) => {record.doctorInfo.phoneNumber},
69 | },
70 | {
71 | title: "Date & Time",
72 | dataIndex: "createdAt",
73 | render: (text, record) => (
74 |
75 | {moment(record.date).format("DD-MM-YYYY")}{" "}
76 | {moment(record.time).format("HH:mm")}
77 |
78 | ),
79 | },
80 | {
81 | title: "Status",
82 | dataIndex: "status",
83 | },
84 | {
85 | title: "Actions",
86 | dataIndex: "actions",
87 | render: (text, record) => (
88 |
89 | {record.status === "pending" && (
90 |
91 |
changeAppointmentStatus(record, "approved")}
94 | >
95 | Approve
96 |
97 | changeAppointmentStatus(record, "rejected")}
100 | >
101 | Reject
102 |
103 |
104 | )}
105 |
106 | ),
107 | },
108 | ];
109 | useEffect(() => {
110 | getAppointmentsData();
111 | }, []);
112 | return (
113 |
114 | Appointments
115 |
116 |
117 |
118 | );
119 | }
120 |
121 | export default DoctorAppointments;
122 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter, Routes, Route } from "react-router-dom";
3 | import Login from "./pages/Login";
4 | import Register from "./pages/Register";
5 | import { Button } from "antd";
6 | import { Toaster } from "react-hot-toast";
7 | import Home from "./pages/Home";
8 | import { useSelector } from "react-redux";
9 | import ProtectedRoute from "./components/ProtectedRoute";
10 | import PublicRoute from "./components/PublicRoute";
11 | import ApplyDoctor from "./pages/ApplyDoctor";
12 | import Notifications from "./pages/Notifications";
13 | import Userslist from "./pages/Admin/Userslist";
14 | import DoctorsList from "./pages/Admin/DoctorsList";
15 | import Profile from "./pages/Doctor/Profile";
16 | import BookAppointment from "./pages/BookAppointment";
17 | import Appointments from "./pages/Appointments";
18 | import DoctorAppointments from "./pages/Doctor/DoctorAppointments";
19 |
20 | function App() {
21 | const { loading } = useSelector((state) => state.alerts);
22 | return (
23 |
24 | {loading && (
25 |
28 | )}
29 |
30 |
31 |
35 |
36 |
37 | }
38 | />
39 |
43 |
44 |
45 | }
46 | />
47 |
51 |
52 |
53 | }
54 | />
55 |
59 |
60 |
61 | }
62 | />
63 |
67 |
68 |
69 | }
70 | />
71 |
75 |
76 |
77 | }
78 | />
79 |
80 |
84 |
85 |
86 | }
87 | />
88 |
89 |
93 |
94 |
95 | }
96 | />
97 |
98 |
102 |
103 |
104 | }
105 | />
106 |
110 |
111 |
112 | }
113 | />
114 |
115 |
119 |
120 |
121 | }
122 | />
123 |
124 |
125 | );
126 | }
127 |
128 | export default App;
129 |
--------------------------------------------------------------------------------
/client/src/components/DoctorForm.js:
--------------------------------------------------------------------------------
1 | import { Button, Col, Form, Input, Row, TimePicker } from "antd";
2 | import moment from "moment";
3 | import React from "react";
4 |
5 | function DoctorForm({ onFinish, initivalValues }) {
6 | return (
7 |
124 | );
125 | }
126 |
127 | export default DoctorForm;
128 |
--------------------------------------------------------------------------------
/client/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "../layout.css";
3 | import { Link, useLocation, useNavigate } from "react-router-dom";
4 | import { useSelector } from "react-redux";
5 | import { Badge } from "antd";
6 |
7 | function Layout({ children }) {
8 | const [collapsed, setCollapsed] = useState(false);
9 | const { user } = useSelector((state) => state.user);
10 |
11 | const navigate = useNavigate();
12 | const location = useLocation();
13 | const userMenu = [
14 | {
15 | name: "Home",
16 | path: "/",
17 | icon: "ri-home-line",
18 | },
19 | {
20 | name: "Appointments",
21 | path: "/appointments",
22 | icon: "ri-file-list-line",
23 | },
24 | {
25 | name: "Apply Doctor",
26 | path: "/apply-doctor",
27 | icon: "ri-hospital-line",
28 | }
29 | ];
30 |
31 | const doctorMenu = [
32 | {
33 | name: "Home",
34 | path: "/",
35 | icon: "ri-home-line",
36 | },
37 | {
38 | name: "Appointments",
39 | path: "/doctor/appointments",
40 | icon: "ri-file-list-line",
41 | },
42 | {
43 | name: "Profile",
44 | path: `/doctor/profile/${user?._id}`,
45 | icon: "ri-user-line",
46 | },
47 | ];
48 |
49 | const adminMenu = [
50 | {
51 | name: "Home",
52 | path: "/",
53 | icon: "ri-home-line",
54 | },
55 | {
56 | name: "Users",
57 | path: "/admin/userslist",
58 | icon: "ri-user-line",
59 | },
60 | {
61 | name: "Doctors",
62 | path: "/admin/doctorslist",
63 | icon: "ri-user-star-line",
64 | },
65 | {
66 | name: "Profile",
67 | path: "/profile",
68 | icon: "ri-user-line",
69 | },
70 | ];
71 |
72 | const menuToBeRendered = user?.isAdmin ? adminMenu : user?.isDoctor ? doctorMenu : userMenu;
73 | const role = user?.isAdmin ? "Admin" : user?.isDoctor ? "Doctor" : "User";
74 | return (
75 |
76 |
77 |
78 |
79 |
SH
80 | {role}
81 |
82 |
83 |
84 | {menuToBeRendered.map((menu) => {
85 | const isActive = location.pathname === menu.path;
86 | return (
87 |
92 |
93 | {!collapsed && {menu.name}}
94 |
95 | );
96 | })}
97 |
{
100 | localStorage.clear();
101 | navigate("/login");
102 | }}
103 | >
104 |
105 | {!collapsed && Logout}
106 |
107 |
108 |
109 |
110 |
111 |
112 | {collapsed ? (
113 |
setCollapsed(false)}
116 | >
117 | ) : (
118 |
setCollapsed(true)}
121 | >
122 | )}
123 |
124 |
125 | navigate("/notifications")}
128 | >
129 |
130 |
131 |
132 |
133 | {user?.name}
134 |
135 |
136 |
137 |
138 |
{children}
139 |
140 |
141 |
142 | );
143 | }
144 |
145 | export default Layout;
146 |
--------------------------------------------------------------------------------
/client/src/pages/BookAppointment.js:
--------------------------------------------------------------------------------
1 | import { Button, Col, DatePicker, Form, Input, Row, TimePicker } from "antd";
2 | import React, { useEffect, useState } from "react";
3 | import Layout from "../components/Layout";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { showLoading, hideLoading } from "../redux/alertsSlice";
6 | import { toast } from "react-hot-toast";
7 | import axios from "axios";
8 | import { Navigate, useNavigate, useParams } from "react-router-dom";
9 | import DoctorForm from "../components/DoctorForm";
10 | import moment from "moment";
11 |
12 | function BookAppointment() {
13 | const [isAvailable, setIsAvailable] = useState(false);
14 | const navigate = useNavigate();
15 | const [date, setDate] = useState();
16 | const [time, setTime] = useState();
17 | const { user } = useSelector((state) => state.user);
18 | const [doctor, setDoctor] = useState(null);
19 | const params = useParams();
20 | const dispatch = useDispatch();
21 |
22 | const getDoctorData = async () => {
23 | try {
24 | dispatch(showLoading());
25 | const response = await axios.post(
26 | "/api/doctor/get-doctor-info-by-id",
27 | {
28 | doctorId: params.doctorId,
29 | },
30 | {
31 | headers: {
32 | Authorization: `Bearer ${localStorage.getItem("token")}`,
33 | },
34 | }
35 | );
36 |
37 | dispatch(hideLoading());
38 | if (response.data.success) {
39 | setDoctor(response.data.data);
40 | }
41 | } catch (error) {
42 | console.log(error);
43 | dispatch(hideLoading());
44 | }
45 | };
46 | const checkAvailability = async () => {
47 | try {
48 | dispatch(showLoading());
49 | const response = await axios.post(
50 | "/api/user/check-booking-avilability",
51 | {
52 | doctorId: params.doctorId,
53 | date: date,
54 | time: time,
55 | },
56 | {
57 | headers: {
58 | Authorization: `Bearer ${localStorage.getItem("token")}`,
59 | },
60 | }
61 | );
62 | dispatch(hideLoading());
63 | if (response.data.success) {
64 | toast.success(response.data.message);
65 | setIsAvailable(true);
66 | } else {
67 | toast.error(response.data.message);
68 | }
69 | } catch (error) {
70 | toast.error("Error booking appointment");
71 | dispatch(hideLoading());
72 | }
73 | };
74 | const bookNow = async () => {
75 | setIsAvailable(false);
76 | try {
77 | dispatch(showLoading());
78 | const response = await axios.post(
79 | "/api/user/book-appointment",
80 | {
81 | doctorId: params.doctorId,
82 | userId: user._id,
83 | doctorInfo: doctor,
84 | userInfo: user,
85 | date: date,
86 | time: time,
87 | },
88 | {
89 | headers: {
90 | Authorization: `Bearer ${localStorage.getItem("token")}`,
91 | },
92 | }
93 | );
94 |
95 | dispatch(hideLoading());
96 | if (response.data.success) {
97 |
98 | toast.success(response.data.message);
99 | navigate('/appointments')
100 | }
101 | } catch (error) {
102 | toast.error("Error booking appointment");
103 | dispatch(hideLoading());
104 | }
105 | };
106 |
107 | useEffect(() => {
108 | getDoctorData();
109 | }, []);
110 | return (
111 |
112 | {doctor && (
113 |
114 |
115 | {doctor.firstName} {doctor.lastName}
116 |
117 |
118 |
119 |
120 |
121 |
127 |
128 |
129 |
130 | Timings : {doctor.timings[0]} - {doctor.timings[1]}
131 |
132 |
133 | Phone Number :
134 | {doctor.phoneNumber}
135 |
136 |
137 | Address :
138 | {doctor.address}
139 |
140 |
141 | Fee per Visit :
142 | {doctor.feePerCunsultation}
143 |
144 |
145 | Website :
146 | {doctor.website}
147 |
148 |
149 | {
152 | setDate(moment(value).format("DD-MM-YYYY"));
153 | setIsAvailable(false);
154 | }}
155 | />
156 | {
160 | setIsAvailable(false);
161 | setTime(moment(value).format("HH:mm"));
162 | }}
163 | />
164 | {!isAvailable && }
170 |
171 | {isAvailable && (
172 |
178 | )}
179 |
180 |
181 |
182 |
183 |
184 | )}
185 |
186 | );
187 | }
188 |
189 | export default BookAppointment;
190 |
--------------------------------------------------------------------------------
/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const User = require("../models/userModel");
4 | const Doctor = require("../models/doctorModel");
5 | const bcrypt = require("bcryptjs");
6 | const jwt = require("jsonwebtoken");
7 | const authMiddleware = require("../middlewares/authMiddleware");
8 | const Appointment = require("../models/appointmentModel");
9 | const moment = require("moment");
10 |
11 | router.post("/register", async (req, res) => {
12 | try {
13 | const userExists = await User.findOne({ email: req.body.email });
14 | if (userExists) {
15 | return res
16 | .status(200)
17 | .send({ message: "User already exists", success: false });
18 | }
19 | const password = req.body.password;
20 | const salt = await bcrypt.genSalt(10);
21 | const hashedPassword = await bcrypt.hash(password, salt);
22 | req.body.password = hashedPassword;
23 | const newuser = new User(req.body);
24 | await newuser.save();
25 | res
26 | .status(200)
27 | .send({ message: "User created successfully", success: true });
28 | } catch (error) {
29 | console.log(error);
30 | res
31 | .status(500)
32 | .send({ message: "Error creating user", success: false, error });
33 | }
34 | });
35 |
36 | router.post("/login", async (req, res) => {
37 | try {
38 | const user = await User.findOne({ email: req.body.email });
39 | if (!user) {
40 | return res
41 | .status(200)
42 | .send({ message: "User does not exist", success: false });
43 | }
44 | const isMatch = await bcrypt.compare(req.body.password, user.password);
45 | if (!isMatch) {
46 | return res
47 | .status(200)
48 | .send({ message: "Password is incorrect", success: false });
49 | } else {
50 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
51 | expiresIn: "1d",
52 | });
53 | res
54 | .status(200)
55 | .send({ message: "Login successful", success: true, data: token });
56 | }
57 | } catch (error) {
58 | console.log(error);
59 | res
60 | .status(500)
61 | .send({ message: "Error logging in", success: false, error });
62 | }
63 | });
64 |
65 | router.post("/get-user-info-by-id", authMiddleware, async (req, res) => {
66 | try {
67 | const user = await User.findOne({ _id: req.body.userId });
68 | user.password = undefined;
69 | if (!user) {
70 | return res
71 | .status(200)
72 | .send({ message: "User does not exist", success: false });
73 | } else {
74 | res.status(200).send({
75 | success: true,
76 | data: user,
77 | });
78 | }
79 | } catch (error) {
80 | res
81 | .status(500)
82 | .send({ message: "Error getting user info", success: false, error });
83 | }
84 | });
85 |
86 | router.post("/apply-doctor-account", authMiddleware, async (req, res) => {
87 | try {
88 | const newdoctor = new Doctor({ ...req.body, status: "pending" });
89 | await newdoctor.save();
90 | const adminUser = await User.findOne({ isAdmin: true });
91 |
92 | const unseenNotifications = adminUser.unseenNotifications;
93 | unseenNotifications.push({
94 | type: "new-doctor-request",
95 | message: `${newdoctor.firstName} ${newdoctor.lastName} has applied for a doctor account`,
96 | data: {
97 | doctorId: newdoctor._id,
98 | name: newdoctor.firstName + " " + newdoctor.lastName,
99 | },
100 | onClickPath: "/admin/doctorslist",
101 | });
102 | await User.findByIdAndUpdate(adminUser._id, { unseenNotifications });
103 | res.status(200).send({
104 | success: true,
105 | message: "Doctor account applied successfully",
106 | });
107 | } catch (error) {
108 | console.log(error);
109 | res.status(500).send({
110 | message: "Error applying doctor account",
111 | success: false,
112 | error,
113 | });
114 | }
115 | });
116 | router.post(
117 | "/mark-all-notifications-as-seen",
118 | authMiddleware,
119 | async (req, res) => {
120 | try {
121 | const user = await User.findOne({ _id: req.body.userId });
122 | const unseenNotifications = user.unseenNotifications;
123 | const seenNotifications = user.seenNotifications;
124 | seenNotifications.push(...unseenNotifications);
125 | user.unseenNotifications = [];
126 | user.seenNotifications = seenNotifications;
127 | const updatedUser = await user.save();
128 | updatedUser.password = undefined;
129 | res.status(200).send({
130 | success: true,
131 | message: "All notifications marked as seen",
132 | data: updatedUser,
133 | });
134 | } catch (error) {
135 | console.log(error);
136 | res.status(500).send({
137 | message: "Error applying doctor account",
138 | success: false,
139 | error,
140 | });
141 | }
142 | }
143 | );
144 |
145 | router.post("/delete-all-notifications", authMiddleware, async (req, res) => {
146 | try {
147 | const user = await User.findOne({ _id: req.body.userId });
148 | user.seenNotifications = [];
149 | user.unseenNotifications = [];
150 | const updatedUser = await user.save();
151 | updatedUser.password = undefined;
152 | res.status(200).send({
153 | success: true,
154 | message: "All notifications cleared",
155 | data: updatedUser,
156 | });
157 | } catch (error) {
158 | console.log(error);
159 | res.status(500).send({
160 | message: "Error applying doctor account",
161 | success: false,
162 | error,
163 | });
164 | }
165 | });
166 |
167 | router.get("/get-all-approved-doctors", authMiddleware, async (req, res) => {
168 | try {
169 | const doctors = await Doctor.find({ status: "approved" });
170 | res.status(200).send({
171 | message: "Doctors fetched successfully",
172 | success: true,
173 | data: doctors,
174 | });
175 | } catch (error) {
176 | console.log(error);
177 | res.status(500).send({
178 | message: "Error applying doctor account",
179 | success: false,
180 | error,
181 | });
182 | }
183 | });
184 |
185 | router.post("/book-appointment", authMiddleware, async (req, res) => {
186 | try {
187 | req.body.status = "pending";
188 | req.body.date = moment(req.body.date, "DD-MM-YYYY").toISOString();
189 | req.body.time = moment(req.body.time, "HH:mm").toISOString();
190 | const newAppointment = new Appointment(req.body);
191 | await newAppointment.save();
192 | //pushing notification to doctor based on his userid
193 | const user = await User.findOne({ _id: req.body.doctorInfo.userId });
194 | user.unseenNotifications.push({
195 | type: "new-appointment-request",
196 | message: `A new appointment request has been made by ${req.body.userInfo.name}`,
197 | onClickPath: "/doctor/appointments",
198 | });
199 | await user.save();
200 | res.status(200).send({
201 | message: "Appointment booked successfully",
202 | success: true,
203 | });
204 | } catch (error) {
205 | console.log(error);
206 | res.status(500).send({
207 | message: "Error booking appointment",
208 | success: false,
209 | error,
210 | });
211 | }
212 | });
213 |
214 | router.post("/check-booking-avilability", authMiddleware, async (req, res) => {
215 | try {
216 | const date = moment(req.body.date, "DD-MM-YYYY").toISOString();
217 | const fromTime = moment(req.body.time, "HH:mm")
218 | .subtract(1, "hours")
219 | .toISOString();
220 | const toTime = moment(req.body.time, "HH:mm").add(1, "hours").toISOString();
221 | const doctorId = req.body.doctorId;
222 | const appointments = await Appointment.find({
223 | doctorId,
224 | date,
225 | time: { $gte: fromTime, $lte: toTime },
226 | });
227 | if (appointments.length > 0) {
228 | return res.status(200).send({
229 | message: "Appointments not available",
230 | success: false,
231 | });
232 | } else {
233 | return res.status(200).send({
234 | message: "Appointments available",
235 | success: true,
236 | });
237 | }
238 | } catch (error) {
239 | console.log(error);
240 | res.status(500).send({
241 | message: "Error booking appointment",
242 | success: false,
243 | error,
244 | });
245 | }
246 | });
247 |
248 | router.get("/get-appointments-by-user-id", authMiddleware, async (req, res) => {
249 | try {
250 | const appointments = await Appointment.find({ userId: req.body.userId });
251 | res.status(200).send({
252 | message: "Appointments fetched successfully",
253 | success: true,
254 | data: appointments,
255 | });
256 | } catch (error) {
257 | console.log(error);
258 | res.status(500).send({
259 | message: "Error fetching appointments",
260 | success: false,
261 | error,
262 | });
263 | }
264 | });
265 | module.exports = router;
266 |
--------------------------------------------------------------------------------