/Mern-overflow-Pharmacy

Primary LanguageJavaScriptApache License 2.0Apache-2.0

logo

El7a2ny Pharmacy App

El7a2ny Pharmacy is a platform that facilitates a streamlined healthcare experience, catering to the distinct needs of pharmacists and patients. Pharmacists efficiently manage medicine-related activities, monitoring sales, updating medicine details, and receiving real-time notifications about stock levels. They can engage in direct communication with patients, view detailed sales reports, and effectively handle their wallets. On the patient side, the interface is user-friendly, allowing for easy management of wallets, cart items, and orders. Patients can effortlessly check out using multiple payment methods, add new delivery addresses, and receive alternative suggestions for out-of-stock medicines based on active ingredients. This platform prioritizes efficiency, transparency, and personalized healthcare interactions for pharmacists and patients alike.

Badges

TypeScript JavaScript MongoDB Express.js React.js NodeJS JWT Git MUI GitHub Stripe JEST Docker Postman NPM

Table of Contents

Build Status ๐Ÿ”จ

  • The project is currently in development.
  • The project needs to be deployed through a cloud service.
  • The project needs more thorough testing.
  • Need to add screenshots, code examples, and tests to the README

โฌ†

Code Style ๐Ÿ“œ

The code style used is eslint and prettier. The code style is enforced using pre-commit hooks

To use the code style we follow, run these commands

Install pre-commit package by running

> pip install pre-commit

Once installed, run the following for a one-time setup

> pre-commit install

You will then need to run the following command each time before your next commit attempt

> npx prettier . --write

โฌ†

Demo & Screenshots ๐Ÿ“ธ

Authentication
Sign in Page

Sign In Page

Patient Registration

Pharmacist Registration

Patient Profile

Patient Profile

Admin Controls
View Registered Patients

View Registered Patients

Add Admin

Manage System Admins

View Pharamcist Requests

View Pharamcist Requests

Viewing Medicines
Admin view Medicines

Admin view Medicines

Patient view Medicines

Patient view Medicines

Cart
Empty Cart

Empty Cart

Items in Cart

Items in Cart

Order Details
Order Summary

Order Summary

Submitted Order Details

Submitted Order Details

โฌ†

Tech Stack ๐Ÿงฐ๐Ÿ”ง

Client: React, Redux, Material-UI, JavaScript

Server: Node, Express, MongoDB, Mongoose, TypeScript, JWT, Stripe API, Postman, Jest

General: Docker, Git & GitHub, VSCode

โฌ†

Features โœจ

Guests can
  • Sign in to their account
  • Sign up as a patient
  • Request to sign up as a pharmacist
  • Reset forgotten password through OTP sent to email
Logged in System Users can
  • Change their password
  • Sign out
  • View a list of all available medicines (photo, price, description)
  • Search for medicine based on name
  • Filter medicines based on medicinal use
Admins can
  • Add another admin with a set username and password
  • Remove pharmacist/patient/admin from the system
  • View all information uploaded by pharmacists who applied to join the platform
  • Accept or reject pharmacist proposals
  • View a total sales report based on a chosen month
  • View information about any user on the system
Pharmacists can
  • View the available quantity and sales of each medicine
  • Add a medicine with its details, price, and available quantity
  • Upload medicine image
  • Edit medicine details and price
  • Archive / unarchive a medicine
  • View a total sales report based on a chosen month
  • Filter sales report based on a medicine/date
  • Chat with a patient
  • View the amount in my wallet
  • Receive a notification once a medicine is out of stock on the system and via email
Patients can
  • Chat with a pharmacist
  • View the amount in their wallet
  • Add an over-the-counter medicine or a prescription medicine included in their prescriptions in their cart
  • View their cart items
  • Remove an item from their cart
  • Update the amount of an item in their cart
  • Check out their orders with address and payment method (wallet/COD/credit card)
  • Add new delivery addresses
  • View details and status of all their orders
  • Cancel a pending order
  • View alternatives to a medicine that is out of stock based on main active ingredient

โฌ†

Code Examples ๐Ÿ‘‰

Add Address to a Patient
const addAddress = async (req: Request, res: Response) => {
    const patientId = req.params.patientId;
    const newAddress = req.body.newAddress;
    try {
        const existingPatient = await patient.findOne({ _id: patientId });
        if (!existingPatient) {
            return res.status(404).json({ message: "Patient not found" });
        }
        existingPatient.address.push(newAddress);
        await existingPatient.save();
        res.json({ message: "Address added successfully" });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: "Internal Server Error" });
    }
};
Archive / Unarchive a Medicine
const archiveMedicine = async (req: Request, res: Response) => {
    const id = req.params.id;
    try {
        const existingMedicine = await medicine.findById(id);
        if (!existingMedicine) {
            return res.status(404).send({ message: "Medicine not found" });
        }
        existingMedicine.isArchived = !existingMedicine.isArchived;
        const updatedMed = await existingMedicine.save();
        res.status(200).send(updatedMed);
    } catch (error) {
        res.status(400).send(error);
    }
};
List Adminstrators
const listAdminstrators = async (req: Request, res: Response) => {
    const adminstrators = adminstrator
        .find({})
        .then((admns) => res.status(200).json(admns))
        .catch((err) => {
            res.status(400).json(err);
        });
};
Not Found Page Component
import NotFoundImg from "./assets/photos/not-found.png";

<Container>
    <div>
        <h1>404 - Page Not Found</h1>
        <p>Sorry, the page you're looking for does not exist.</p>
        <img src={NotFoundImg} />
    </div>
    <Button variant="contained" component={Link} to="/patient/medicines">
        {" "}
        Return to Homepage{" "}
    </Button>
</Container>;
AppBar Component
<AppBar position="static">
    <Toolbar>
        <IconButton>
            <MenuIcon />
        </IconButton>
        <Typography>{props.title}</Typography>
        {props.cart && (
            <IconButton component={Link} to="/patient/cart">
                <ShoppingCartIcon />
            </IconButton>
        )}
        <IconButton component={Link} onClick={handleLogout}>
            <LogoutIcon />
        </IconButton>
    </Toolbar>
</AppBar>

โฌ†

Installation ๐Ÿ“ฅ

Clone the project

> git clone https://github.com/advanced-computer-lab-2023/Mern-overflow-Pharmacy

Go to the project directory

> cd Mern-overflow-Pharmacy

Install dependencies

> cd server && npm i && cd -
> cd client && npm i && cd -

โฌ†

How to Use โ“

Using Docker

First, you need to build the container. You need to do this the first time only.

> make build

Start the back-end

> make up

Start the client side

> make f-up
Manually

Start the back-end server

> cd server && npm run dev

Start the client side

> cd client && npm start

Environment Variables ๐Ÿ“ƒ

To run this project, you will need to add the following environment variables to your server/.env file. You can find an environment variables file example in server/.env.example

MONGO_URI

PORT

JWT_SECRET

EMAIL

EMAILPASSWORD

โฌ†

API Reference ๐Ÿ“–

Authentication routes
method route returns
POST /auth/login/ Log in
POST /auth/logout/ Log out
POST /auth/reset/ Reset Password
POST /auth/resetwithtoken/ Reset Password with Token
POST /auth/change/ Change Password
Admin routes
method route returns
GET /adminstrators/ View all admins
POST /adminstrators/ Create an admin
DELETE /adminstrators/:id/ Delete an admin
Patient routes
method route returns
GET /patients/ View all patients
GET /patients/:id/ View details of a patient
GET /patients/address/:id/ View addresses of a patient
POST /patients/ Create a patient
PUT /patients/address/:id/ Add an adderss to a patient
DELETE /patients/:id/ Delete a patient
Pharmacist routes
method route returns
GET /pharmacists/ View all accepted pharmacists
GET /pharmacists/listAll/ View all pending pharmacist requests
GET /pharmacists/viewAll/ View all pharmacist requests
GET /pharmacists/:id/ View details of a pharmacist
POST /pharmacists/ Create a pharmacist request
POST /pharmacists/acceptPharmacist/ Accept a pharmacist request
POST /pharmacists/rejectPharmacist/ Reject a pharmacist request
PUT /pharmacists/:id/ Update pharmacist request to accepted
DELETE /pharmacists/:id/ Delete a pharmacist
Medicine routes
method route returns
GET /medicines/ View the sales and quantity of medicines
GET /medicines/view/ View all available medicines only
GET /medicines/viewAll/ View all details about all medicines
GET /medicines/search/ View medicines by selected name query
GET /medicines/filter/ View medicines by selected medicinal use
POST /medicines/ Create a medicine
PUT /medicines/:id/ Update medicine details
PUT /medicines/:id/archive/ Archive / Unarchive a medicine
Cart routes
method route returns
GET /cart/:id/ View cart items
POST /cart/:id/add/ Add a medicine to cart
POST /cart/:id/changeAmount/ Change a medicine's quantity in cart
PUT /cart/:id/empty/ Empty cart
DELETE /cart/:id/:medName/ Remove a medicine by name from cart
Order routes
method route returns
GET /orders/:id/ View orders by patient id
GET /orders/salesreport/ View sales report
POST /orders/:id/add/ Add an order
PUT /orders/:id/ Cancel pending order
Payment routes
method route returns
POST /walletPayment/shoppingCart/ Pay for an order using wallet
POST /create-checkout-session/shoppingCart/ Pay for an order using credit card

โฌ†

Tests ๐Ÿงช

Backend testing is done using jest. To run the tests, run the following command

> cd server && npm run test

Model tests make sure the respective entity models are correct by creating new entities. They also make sure the Models raise the appropriate errors when required (i.e when an email is invalid)

A few examples of model tests in the following snippets:

Check if Admin email valid
test('should throw an error if email is invalid', async () => {
        const admin = new Adminstrator({
            username: 'testuser',
            passwordHash: 'password',
            email: 'invalidemail',
        });
        await expect(admin.save()).rejects.toThrow('invalid email');
    });
Check if Cart has a patient
test('should throw an error if patient is missing', async () => {
        const cartWithoutPatient = {
            medicines: [
                { medName: 'Medicine1', medPrice: 10, medQuantity: 2 },
                { medName: 'Medicine2', medPrice: 15, medQuantity: 3 },
            ],
        };

        const cart = new Cart(cartWithoutPatient);
        await expect(cart.save()).rejects.toThrow('Cart validation failed: patient: Path `patient` is required.');
    });
Check if a Chat has a name
test('should throw an error if chatName is missing', async () => {
        const chatWithoutChatName = {
            isGroupChat: false,
            users: [new Types.ObjectId(), new Types.ObjectId()],
            createdAt: new Date(),
            updatedAt: new Date(),
        };

        const chat = new ChatModel(chatWithoutChatName);
        await expect(chat.save()).rejects.toThrow('Chat validation failed: chatName: Path `chatName` is required.');
    });
Check if a Medicine has a name
test('should throw an error if name is missing', async () => {
    const medicineWithoutName = {
      medicinalUse: 'Pain Relief',
      details: { description: 'Test description', activeIngredients: ['Ingredient1', 'Ingredient2'] },
      price: 20,
      availableQuantity: 100,
      sales: 50,
      image: 'test_image.jpg',
      overTheCounter: true,
      isArchived: false,
    };

    const medicine = new Medicine(medicineWithoutName);
    await expect(medicine.save()).rejects.toThrow('Medicine validation failed: name: Path `name` is required.');
  });
Check if a Message has a sender
test('should throw an error if sender is missing', async () => {
        const messageWithoutSender = {
            content: 'Test message content',
            chat: new Types.ObjectId(),
            readBy: [new Types.ObjectId()],
            createdAt: new Date(),
            updatedAt: new Date(),
        };

        const message = new MessageModel(messageWithoutSender);
        await expect(message.save()).rejects.toThrow('Message validation failed: sender: Path `sender` is required.');
    });
Check if an Order has a patient
test('should throw an error if patient is missing', async () => {
        const orderWithoutPatient = {
            status: 'pending',
            date: new Date(),
            total: 100,
            address: 'Test address',
            paymentMethod: 'cash on delivery',
            medicines: [{ medName: 'Medicine1', medPrice: 10, medQuantity: 2 }],
        };

        const order = new Order(orderWithoutPatient);
        await expect(order.save()).rejects.toThrow('Order validation failed: patient: Path `patient` is required.');
    });
Check if a Package has a name
test('should throw an error if name is missing', async () => {
        const packageWithoutName = {
            price: 50,
            discountOnDoctorSessions: 10,
            discountOnMedicine: 5,
            discountForFamily: 15,
        };

        const packageItem = new Package(packageWithoutName);
        await expect(packageItem.save()).rejects.toThrow('Package validation failed: name: Path `name` is required.');
    });
Check if a Patient has a name
test('should throw an error if name is missing', async () => {
        const patientWithoutName = {
            email: "email@gmail.com",
            passwordHash: "password",
            username: "username",
            nationalId: '123456789',
            dateOfBirth: new Date(),
            gender: 'male',
            mobileNumber: '+12345678',
            emergencyContact: {
                name: 'EmergencyContact',
                mobileNumber: '+12345678',
                relation: 'parent',
            },
        };

        const patient = new Patient(patientWithoutName);
        await expect(patient.save()).rejects.toThrow('Patient validation failed: name: Path `name` is required.');
    });
Check if a Pharmacist has a date of birth
test('should throw an error if dateOfBirth is missing', async () => {
        const pharmacistWithoutDateOfBirth = {
            email: "email@gmail.com",
            passwordHash: "password",
            username: "username",
            name: 'John Doe',
            hourlyRate: 50,
            affiliation: 'Pharmacy ABC',
            education: 'Pharmacist Degree',
            files: [{ filename: 'file1.txt', path: '/path/to/file1.txt' }],
            status: 'pending',
        };

        const pharmacist = new Pharmacist(pharmacistWithoutDateOfBirth);
        await expect(pharmacist.save()).rejects.toThrow('pharmacist validation failed: dateOfBirth: Path `dateOfBirth` is required.');
    });
Check if a Prescription has a doctor reference
test('should throw an error if doctor is missing', async () => {
        const prescriptionWithoutDoctor = {
            patient: new Types.ObjectId(),
            medicine: [{ medId: new Types.ObjectId(), dailyDosage: 1 }],
            date: new Date(),
            filled: false,
        };

        const prescription = new Prescription(prescriptionWithoutDoctor);
        await expect(prescription.save()).rejects.toThrow('Prescription validation failed: doctor: Path `doctor` is required.');
    });

image

โฌ†

Contribute ๐Ÿค

Contributions are always welcome!

Getting Started

  1. Fork the repository
  2. Clone the repository
  3. Install dependencies
  4. Create a new branch
  5. Make your changes
  6. Commit and push your changes
  7. Create a pull request
  8. Wait for your pull request to be reviewed and merged

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. Please read the full text so that you can understand what actions will and will not be tolerated.

โฌ†

Credits ๐Ÿ™

โฌ†

Authors ๐Ÿง‘โ€๐Ÿ’ป๏ธ

Abdelrahman Salah Omar Wael John Fayez Ahmed Wael Mohamed Mohey
Ahmed Yasser Alaa Aref Ibrahim Soltan Logine Mohamed Mohamed Elshekha

โฌ†

License โš–๏ธ

  • This software product is open source under the Apache 2.0 License.

  • Stripe is licensed under the Apache License 2.0

โฌ†

Feedback โญ

We would love to hear from you. If you have any feedback, please reach out to us at john.f.roufaeil@gmail.com