├── .github ├── Issue_Template │ ├── Bug-report.yaml │ ├── Feature-Request.yaml │ └── General-Issue.yaml ├── Pull_request_Template.md └── workflows │ └── autoComment.yaml ├── Code Of Conduct.md ├── LICENSE ├── README.md ├── client ├── .env.local ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── logo.png │ ├── manifest.json │ ├── robots.txt │ └── site.webmanifest ├── src │ ├── App.css │ ├── App.js │ ├── assests │ │ └── visit.jpeg │ ├── assets │ │ ├── admin dash.png │ │ ├── admin.png │ │ ├── doctor dash.png │ │ ├── doctor.png │ │ ├── logo.png │ │ ├── patient dash.png │ │ ├── patient.png │ │ └── robo.png │ ├── core │ │ ├── APICalls.js │ │ └── RTCManager.js │ ├── index.css │ ├── index.js │ ├── pages │ │ ├── Home.jsx │ │ ├── Meet.jsx │ │ ├── admin │ │ │ ├── auth │ │ │ │ └── page.jsx │ │ │ └── page.jsx │ │ ├── components │ │ │ ├── ActionButtons.jsx │ │ │ ├── FAQSection.jsx │ │ │ ├── Loader.jsx │ │ │ ├── Video.jsx │ │ │ ├── WorkingProgress.jsx │ │ │ ├── bot.js │ │ │ └── medibot.js │ │ ├── doctor │ │ │ ├── auth │ │ │ │ └── page.jsx │ │ │ └── dashboard │ │ │ │ └── page.jsx │ │ ├── meet │ │ │ ├── DoctorMeet.jsx │ │ │ └── PatientMeet.jsx │ │ └── patient │ │ │ ├── auth │ │ │ └── page.jsx │ │ │ └── dashboard │ │ │ └── page.jsx │ ├── styles │ │ └── chatbot.css │ └── utils │ │ └── socket.js └── tailwind.config.js └── server ├── .env.local ├── .gitignore ├── ca.crt ├── ca.key ├── cert.crt ├── cert.key ├── dist ├── core │ ├── Doctor.js │ ├── PDManager.js │ ├── Patient.js │ └── Session.js ├── index.js ├── libs │ └── uid.js ├── middleware │ └── superAdminMiddleware.js ├── models │ ├── Admin.js │ ├── Doctor.js │ └── Patient.js ├── router │ ├── adminRouter.js │ └── authRouter.js ├── types │ ├── role.js │ └── tokenDetails.js └── utils │ └── connectDB.js ├── package-lock.json ├── package.json ├── src ├── core │ ├── Doctor.ts │ ├── PDManager.ts │ ├── Patient.ts │ └── Session.ts ├── index.ts ├── libs │ └── uid.ts ├── middleware │ └── superAdminMiddleware.ts ├── models │ ├── Admin.ts │ ├── Doctor.ts │ └── Patient.ts ├── router │ ├── adminRouter.ts │ └── authRouter.ts ├── types │ ├── role.ts │ └── tokenDetails.ts └── utils │ └── connectDB.ts └── tsconfig.json /.github/Issue_Template/Bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: ​🐞 Bug 2 | description: Report an issue to help us improve the project. 3 | title: "[BUG] " 4 | labels: ["bug", "goal: fix", "priority: medium"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Description 9 | description: A brief description of the issue or bug you are facing, also include what you tried and what didn't work. 10 | validations: 11 | required: false 12 | - type: textarea 13 | attributes: 14 | label: Screenshots 15 | description: Please add screenshots if applicable 16 | validations: 17 | required: false 18 | - type: textarea 19 | attributes: 20 | label: Any additional information? 21 | description: Any additional information or Is there anything we should know about this bug? 22 | validations: 23 | required: false 24 | - type: dropdown 25 | attributes: 26 | label: What browser are you seeing the problem on? 27 | multiple: true 28 | options: 29 | - Firefox 30 | - Chrome 31 | - Safari 32 | - Microsoft Edge -------------------------------------------------------------------------------- /.github/Issue_Template/Feature-Request.yaml: -------------------------------------------------------------------------------- 1 | name: "✨ Feature Request" 2 | description: "Suggest a new feature to enhance our Quick-Heal Project" 3 | title: "[FEATURE] " 4 | labels: ["enhancement"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | # Feature Request 11 | Thanks for taking the time to suggest a new feature! 12 | Please fill out the information below to help us understand your suggestion better. 13 | 14 | - type: textarea 15 | id: problem 16 | attributes: 17 | label: "Problem Statement" 18 | description: "What problem does this feature solve?" 19 | placeholder: "I'm always frustrated when..." 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: "Proposed Solution" 27 | description: "Describe the solution you'd like" 28 | placeholder: "It would be great if..." 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | id: alternatives 34 | attributes: 35 | label: "Alternative Solutions" 36 | description: "What alternatives have you considered?" 37 | placeholder: "Another approach could be..." 38 | 39 | - type: textarea 40 | id: screenshots 41 | attributes: 42 | label: "Screenshots or Mockups" 43 | description: "Add any relevant screenshots, mockups or examples" 44 | placeholder: "Drag and drop images here..." 45 | 46 | - type: dropdown 47 | id: priority 48 | attributes: 49 | label: "Priority" 50 | options: 51 | - High 52 | - Medium 53 | - Low 54 | validations: 55 | required: true 56 | 57 | - type: checkboxes 58 | id: terms 59 | attributes: 60 | label: "Contribution Guidelines" 61 | options: 62 | - label: "I have searched [existing issues](https://github.com/joefelx/quickheal/issues) and this is not a duplicate" 63 | required: true 64 | - label: "I would like to implement this feature" 65 | required: false 66 | 67 | - type: markdown 68 | attributes: 69 | value: | 70 | --- 71 | 💡 Thank you for contributing to make our Quick Heal Project better! -------------------------------------------------------------------------------- /.github/Issue_Template/General-Issue.yaml: -------------------------------------------------------------------------------- 1 | name: "🤔 General Issue" 2 | description: "Submit a general question, suggestion or issue" 3 | title: "[GENERAL] " 4 | labels: ["triage-needed"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | # General Issue Template 11 | Thanks for taking the time to fill out this issue! Please provide as much information as possible. 12 | 13 | - type: dropdown 14 | id: category 15 | attributes: 16 | label: "Category" 17 | description: "What type of issue is this?" 18 | options: 19 | - Question 20 | - Suggestion 21 | - Problem 22 | - Other 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: "Description" 30 | description: "What would you like to share or ask?" 31 | placeholder: "Provide details about your issue, question, or suggestion..." 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: additional 37 | attributes: 38 | label: "Additional Context" 39 | description: "Add any other context, screenshots, or relevant information" 40 | placeholder: "Add any additional details here..." 41 | 42 | - type: dropdown 43 | id: impact 44 | attributes: 45 | label: "Impact Level" 46 | options: 47 | - High 48 | - Medium 49 | - Low 50 | validations: 51 | required: true 52 | 53 | - type: checkboxes 54 | id: terms 55 | attributes: 56 | label: "Contribution Guidelines" 57 | options: 58 | - label: "I have searched [existing issues](https://github.com/joefelx/quickheal/issues) to avoid duplicates" 59 | required: true 60 | - label: "I would like to help resolve this issue" 61 | required: false 62 | 63 | - type: markdown 64 | attributes: 65 | value: | 66 | --- 67 | Thank you for contributing to our project! 🙏 -------------------------------------------------------------------------------- /.github/Pull_request_Template.md: -------------------------------------------------------------------------------- 1 | # Pull Request Template 2 | 3 | ## Summary 4 | Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. 5 | 6 | Fixes # (issue) 7 | 8 | ## Type of Change 9 | Please mark [X] for applicable items: 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] Documentation update 15 | - [ ] Code refactoring 16 | - [ ] Other (please describe): 17 | 18 | ## Testing 19 | Please describe the tests you performed to verify your changes: 20 | 21 | ## Screenshots/Videos 22 | Please attach relevant screenshots or videos demonstrating the changes. 23 | 24 | ## Checklist 25 | Please mark [X] for completed items: 26 | 27 | - [ ] My code follows the project's style guidelines 28 | - [ ] I have performed a self-review of my code 29 | - [ ] I have commented my code, particularly in hard-to-understand areas 30 | - [ ] I have updated the documentation accordingly 31 | - [ ] My changes generate no new warnings 32 | - [ ] I have added tests that prove my fix is effective or that my feature works 33 | -------------------------------------------------------------------------------- /.github/workflows/autoComment.yaml: -------------------------------------------------------------------------------- 1 | name: Auto Comment on Issues and PRs 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | pull_request: 7 | types: 8 | - opened 9 | 10 | jobs: 11 | auto-comment: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | 20 | - name: Add Comment on Pull Request 21 | if: ${{ github.event_name == 'pull_request' }} 22 | uses: peter-evans/create-or-update-comment@v3 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | repository: ${{ github.repository }} 26 | issue-number: ${{ github.event.pull_request.number }} 27 | body: | 28 | 🚀 **Hello @${{ github.event.pull_request.user.login }}!** 29 | 30 | Thank you for your contribution to the **QuickHeal** project! 🎉 31 | We value your effort and are excited to review your changes. 32 | 33 | ### PR Checklist: 34 | Please ensure your PR adheres to the following checklist: 35 | - [ ] PR description includes a summary and context for the changes. 36 | - [ ] Relevant dependencies have been listed. 37 | - [ ] Testing section is filled out with details on verification. 38 | - [ ] Screenshots/Videos are attached (if applicable). 39 | - [ ] Checklist items are marked as completed. 40 | 41 | ### Review Notifications: 42 | - **Project Admin:** @joefelx 43 | 44 | The team will review your PR shortly. If you have any questions, feel free to ask here! 45 | Happy Coding! 🚀 46 | 47 | - name: Add Comment on Issue 48 | if: ${{ github.event_name == 'issues' }} 49 | uses: peter-evans/create-or-update-comment@v3 50 | with: 51 | token: ${{ secrets.GITHUB_TOKEN }} 52 | repository: ${{ github.repository }} 53 | issue-number: ${{ github.event.issue.number }} 54 | body: | 55 | 👋 **Hello @${{ github.event.issue.user.login }}!** 56 | 57 | Thank you for raising an issue in the **QuickHeal** project! 58 | Your issue has been logged, and the team will review it shortly. 59 | 60 | ### Issue Handling Checklist: 61 | - [ ] Make sure the issue includes clear steps to reproduce (if applicable). 62 | - [ ] Provide relevant context, screenshots, or logs. 63 | - [ ] Mention if this issue blocks any critical workflows. 64 | 65 | ### Notifications: 66 | - **Project Admin:** @joefelx 67 | 68 | We'll get back to you soon. Stay tuned! 🚀 -------------------------------------------------------------------------------- /Code Of Conduct.md: -------------------------------------------------------------------------------- 1 | # **QuickHeal Web App - Code of Conduct** 2 | 3 | At QuickHeal, we strive to create a positive and inclusive environment for all users, developers, and contributors. This Code of Conduct outlines the standards of behavior expected within our community, ensuring that all interactions remain respectful, productive, and conducive to the project's success. 4 | 5 | ## **1. Respectful Communication** 6 | - Always communicate respectfully with fellow users, contributors, and administrators. 7 | - Be mindful of your tone in both written and verbal communication. Avoid offensive, inappropriate, or discriminatory language. 8 | - If disagreements arise, approach them constructively with a focus on finding solutions. 9 | 10 | ## **2. Inclusivity and Diversity** 11 | - We welcome and value individuals from diverse backgrounds, including different genders, races, cultures, and experiences. 12 | - Discrimination or harassment based on race, gender, sexual orientation, disability, religion, or any other status will not be tolerated. 13 | - Ensure that the app and related communication are accessible to all users, including those with disabilities. 14 | 15 | ## **3. Collaboration and Contribution** 16 | - Respect the work of others, giving credit where it’s due. Always acknowledge the contributions of fellow team members and developers. 17 | - When contributing to the project, follow the established coding standards, naming conventions, and guidelines. 18 | - Use version control (Git) to submit changes responsibly and ensure that your contributions align with the project’s goals. 19 | 20 | ## **4. Privacy and Data Security** 21 | - Respect the privacy and confidentiality of all users. Avoid sharing or exploiting sensitive user data. 22 | - Always prioritize user privacy and data security in both development and use of the QuickHeal web app. 23 | - Ensure that any changes made to the app do not compromise its security features. 24 | 25 | ## **5. Accountability** 26 | - Take responsibility for your actions and contributions within the project. If an issue arises, take proactive steps to resolve it. 27 | - Report any potential security vulnerabilities or bugs promptly to the project administrators or through the appropriate channels. 28 | 29 | ## **6. Professionalism** 30 | - Whether you’re a patient, doctor, admin, or contributor, professionalism should be maintained at all times. 31 | - Ensure that your actions in the app are ethical and aligned with the values of the QuickHeal project. 32 | - Be open to feedback and willing to improve based on constructive criticism. 33 | 34 | ## **7. No Spamming or Abusive Behavior** 35 | - Do not engage in spamming, trolling, or any abusive behavior that disrupts the community. 36 | - Use the platform for its intended purposes only: medical consultations, queries, and prescriptions. 37 | 38 | ## **8. Reporting Violations** 39 | - If you observe any violation of this Code of Conduct, please report it to the administrators or use the designated reporting channels. 40 | - Violations will be reviewed, and appropriate action will be taken, which may include removal from the platform or contributor access. 41 | 42 | --- 43 | 44 | **By using QuickHeal, you agree to adhere to this Code of Conduct and help us maintain a positive and respectful environment for all.** 45 | 46 | --- 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 joefelx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *QuickHeal Web App* 2 | Revolutionizing Healthcare with Seamless Video Consultations, Query Management, and Prescriptions 3 | 4 | --- 5 | 6 | ## *Overview* 7 | QuickHeal is a comprehensive healthcare web app designed to connect patients and doctors effortlessly. 8 | - Patients can consult with doctors via *video calls*, manage their **queries**, and receive **prescriptions**. 9 | - Doctors can manage *availability*, respond to queries, and provide prescriptions efficiently. 10 | - The app also features an *admin panel* for management, accessible via specific routes. 11 | 12 | --- 13 | 14 | ## *Key Features* 15 | 16 | ### *For Patients* 17 | - *Video Consultations*: Initiate video calls with available doctors using **Socket.io** for real-time communication. 18 | 19 | ### *For Doctors* 20 | - *Availability Status*: Toggle status (Available/Unavailable) to indicate readiness for consultations. 21 | - *Query Management*: View and respond to patient queries. 22 | 23 | ### *Admin Panel* (Accessible via routes) 24 | - /admin: Main admin dashboard. 25 | - /admin/auth: Authentication page for admin login. 26 | (Not part of visible website navigation but accessible via direct URL routes.) 27 | 28 | --- 29 | 30 | ## *Tech Stack* 31 | 32 | - *Frontend*: ⚛️ React, 🌊 Tailwind CSS 33 | - *Backend*: 🟢 Node.js, ⚡ Express.js 34 | - *Database*: 🍃 MongoDB 35 | - *Real-Time Communication*: 📡 Socket.io 36 | - *Video Call Integration*: 📹 WebRTC/Third-Party API 37 | - *Authentication*: 🔐 Bcrypt Password Hashing for Patients, Doctors, and Admin. 38 | - *Hosting*: Vercel, Render 39 | 40 | --- 41 | 42 | ## *Live Demo* 43 | 🔗 Visit QuickHeal: [QuickHeal Web App](https://quickheal.vercel.app/) 44 | 45 | --- 46 | 47 | ## *Screenshots* 48 | ### *Patient Dashboard* 49 | ![Patient Dashboard](https://github.com/user-attachments/assets/171ec425-980e-44bc-a4fc-d37dab488663) 50 | 51 | ### *Doctor Panel* 52 | ![Doctor Panel](https://github.com/user-attachments/assets/683411e1-5c8d-4c4e-ae12-3de6b244c9d2) 53 | 54 | ### *Video Consultation* 55 | ![Video Consultation](https://github.com/user-attachments/assets/25f55d01-60d9-43ae-a68d-cd1a4e82019a) 56 | 57 | 58 | --- 59 | 60 | ## *Setup Guidelines* 61 | 62 | This guide outlines the setup process for the Quick Heal project, including environment configuration and client/server setup. 63 | 64 | --- 65 | 66 | ### **Prerequisites** 67 | 68 | - **Node.js**: Install the latest LTS version of Node.js. 69 | - **Package Manager**: Use `npm` for installation of packages. 70 | - **Database**: Ensure MongoDB is installed and running locally or on a server. 71 | - **TypeScript**: Install globally if not already available. 72 | 73 | ```bash 74 | npm install -g typescript 75 | ``` 76 | 77 | - **Nodemon**: Install globally for server development. 78 | 79 | ```bash 80 | npm install -g nodemon 81 | ``` 82 | 83 | --- 84 | 85 | ### **Clone the Repository** 86 | 87 | 1. **Clone the Project** 88 | 89 | ```bash 90 | git clone https://github.com/joefelx/quickheal.git 91 | ``` 92 | 93 | 2. **Navigate to the Project Directory** 94 | 95 | ```bash 96 | cd quickheal 97 | ``` 98 | 99 | --- 100 | 101 | ### **Environment Setup** 102 | 103 | 104 | Create an `.env` file in both the `client` and `server` directories with the following configurations: 105 | 106 | #### Client 107 | 108 | ```env 109 | REACT_APP_SERVER_URL=http://localhost:5000 110 | REACT_APP_PASSCODE=your_passcode_here 111 | REACT_APP_CHATBOT_API_KEY=your_chatbot_api_key_here 112 | ``` 113 | 114 | #### Server 115 | 116 | ```env 117 | PORT=5000 118 | MONGO_URL=your_mongo_connection_string_here 119 | JWT_SECRET=your_jwt_secret_here 120 | SUPERADMIN_PASSCODE=your_superadmin_passcode_here 121 | ``` 122 | 123 | > Replace the placeholders with your specific configuration values. 124 | 125 | --- 126 | 127 | ### **Setting Up Chatbot Integration** 128 | 129 | 1. **Obtain the Chatbot API Key** 130 | 131 | Visit the [Gemini API documentation](https://ai.google.dev/gemini-api/docs) to sign up and generate your API key for chatbot integration. 132 | 133 | 2. **Update Environment Variables** 134 | 135 | Add the API key to the client `client/.env` file: 136 | 137 | ```env 138 | REACT_APP_CHATBOT_API_KEY=your_chatbot_api_key 139 | ``` 140 | >Replace `your_chatbot_api_key` with the key obtained from Gemini. 141 | 142 | --- 143 | 144 | ### **Client Setup** 145 | 146 | 1. **Navigate to the Client Directory** 147 | 148 | ```bash 149 | cd client 150 | ``` 151 | 152 | 2. **Install Dependencies** 153 | 154 | ```bash 155 | npm install 156 | ``` 157 | 158 | 3. **Start the Development Server** 159 | 160 | ```bash 161 | npm start 162 | ``` 163 | 164 | The client will be available at [http://localhost:3000](http://localhost:3000). 165 | 166 | --- 167 | 168 | ### **Server Setup** 169 | 170 | 1. **Navigate to the Server Directory** 171 | 172 | ```bash 173 | cd server 174 | ``` 175 | 176 | 2. **Install Dependencies** 177 | 178 | ```bash 179 | npm install 180 | ``` 181 | 182 | 3. **Run the Server in Development Mode** 183 | 184 | ```bash 185 | npm run dev 186 | ``` 187 | 188 | The server will be available at [http://localhost:5000](http://localhost:5000). 189 | 190 | --- 191 | 192 | 193 | 194 | ## *Security* 195 | 196 | QuickHeal ensures data privacy and security with: 197 | - 🔐 *Secure Password Storage* for all users (Patients, Doctors, and Admin). 198 | - 🔒 *Secure communication* via HTTPS (if hosted on a secure domain). 199 | 200 | --- 201 | 202 | ## *Future Enhancements* 203 | 204 | - 📱 *Mobile app* for better accessibility. 205 | - 🗂️ *Comprehensive patient medical history* management. 206 | - 🤖 *AI-powered symptom checker* for preliminary diagnosis. 207 | - 🔄 *Integration with pharmacy services* for medication delivery. 208 | 209 | --- 210 | 211 | ## *Core Contributors* 212 | 213 | - *[Joe Felix A](https://github.com/joefelx)* (Backend) 214 | - *[Karim Suhail S](https://github.com/karimsuhail) (Frontend)* 215 | - *[Mohammed Haris Hasan A](https://github.com/Haris-Off)* (Low Level Design) 216 | 217 | --- 218 | 219 | ## *License* 220 | 221 | This project is licensed under the [MIT License](LICENSE). 222 | 223 | --- 224 | 225 | ## *Thank You* 226 | 227 | We appreciate your interest in QuickHeal. 228 | If you have suggestions, feedback, or want to contribute, feel free to reach out. 229 | -------------------------------------------------------------------------------- /client/.env.local: -------------------------------------------------------------------------------- 1 | REACT_APP_SERVER_URL=http://localhost:5000 2 | REACT_APP_PASSCODE=your_passcode_here 3 | REACT_APP_CHATBOT_API_KEY=your_chatbot_api_key_here 4 | -------------------------------------------------------------------------------- /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 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/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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@google/generative-ai": "^0.21.0", 7 | "@heroicons/react": "^1.0.6", 8 | "@testing-library/jest-dom": "^5.17.0", 9 | "@testing-library/react": "^13.4.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "animate.css": "^4.1.1", 12 | "autoprefixer": "^10.4.20", 13 | "axios": "^1.7.8", 14 | "framer-motion": "^11.12.0", 15 | "postcss": "^8.4.49", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "react-hot-toast": "^2.4.1", 19 | "react-icons": "^5.4.0", 20 | "lucide-react": "^0.473.0", 21 | "react-router-dom": "^7.0.1", 22 | "react-scripts": "5.0.1", 23 | "socket.io-client": "^4.8.1", 24 | "tailwindcss": "^3.4.15", 25 | "web-vitals": "^2.1.4" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | }, 51 | "devDependencies": { 52 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Quick Heal 28 | 50 | 51 | 52 | 53 |
54 |
55 |
56 |
57 |
58 | 59 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/public/logo.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Quick Heal", 3 | "name": "Quick Heal", 4 | "icons": [ 5 | { 6 | "src": "logo.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo.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/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | /* General App Styling */ 2 | .App { 3 | text-align: center; 4 | font-family: Arial, sans-serif; /* Consistent and modern font */ 5 | } 6 | 7 | .App-logo { 8 | height: 40vmin; 9 | pointer-events: none; 10 | } 11 | 12 | @media (prefers-reduced-motion: no-preference) { 13 | .App-logo { 14 | animation: App-logo-spin infinite 20s linear; 15 | } 16 | } 17 | 18 | .App-header { 19 | background-color: #282c34; 20 | min-height: 100vh; 21 | display: flex; 22 | flex-direction: column; 23 | align-items: center; 24 | justify-content: center; 25 | font-size: calc(10px + 2vmin); 26 | color: white; 27 | padding: 20px; 28 | box-sizing: border-box; 29 | } 30 | 31 | .App-link { 32 | color: #61dafb; 33 | text-decoration: none; 34 | transition: color 0.3s ease-in-out; 35 | } 36 | 37 | .App-link:hover { 38 | color: #21a1f1; 39 | } 40 | 41 | /* Keyframes for Logo Spin */ 42 | @keyframes App-logo-spin { 43 | from { 44 | transform: rotate(0deg); 45 | } 46 | to { 47 | transform: rotate(360deg); 48 | } 49 | } 50 | 51 | /* Chatbot Container */ 52 | .chatbot-container { 53 | position: fixed; 54 | bottom: 24px; 55 | left: 10px; 56 | height: 70vh; 57 | width: 18.5rem; 58 | background-color: white; 59 | border-radius: 10px 10px 0 10px; 60 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 61 | overflow: hidden; 62 | z-index: 50; 63 | } 64 | 65 | /* Chatbot Header */ 66 | .bot-header { 67 | background-color: #3b82f6; 68 | color: white; 69 | padding: 10px; 70 | font-size: 1.2rem; 71 | font-weight: bold; 72 | text-align: center; 73 | } 74 | 75 | /* Chatbot Messages */ 76 | .bot-messages { 77 | height: calc(100% - 110px); 78 | overflow-y: auto; 79 | padding: 10px; 80 | background-color: #f9fafb; 81 | } 82 | 83 | /* Message Styles */ 84 | .message { 85 | margin-bottom: 10px; 86 | max-width: 80%; 87 | word-wrap: break-word; 88 | } 89 | 90 | .user-message { 91 | background-color: #3b82f6; 92 | color: white; 93 | border-radius: 10px 10px 0 10px; 94 | padding: 8px 12px; 95 | margin-left: auto; 96 | font-size: 0.9rem; 97 | } 98 | 99 | .bot-message { 100 | background-color: #e5e7eb; 101 | color: #1f2937; 102 | border-radius: 10px 10px 10px 0; 103 | padding: 8px 12px; 104 | margin-right: auto; 105 | font-size: 0.9rem; 106 | } 107 | 108 | /* Input Section */ 109 | .bot-input { 110 | display: flex; 111 | padding: 10px; 112 | background-color: white; 113 | border-top: 1px solid #d1d5db; 114 | } 115 | 116 | .bot-input input { 117 | flex-grow: 1; 118 | padding: 8px; 119 | border: 1px solid #d1d5db; 120 | border-radius: 4px 0 0 4px; 121 | font-size: 0.9rem; 122 | color: #1f2937; 123 | background-color: #ffffff; 124 | } 125 | 126 | .bot-input input::placeholder { 127 | color: #9ca3af; 128 | } 129 | 130 | .bot-input button { 131 | background-color: #3b82f6; 132 | color: white; 133 | border: none; 134 | padding: 8px 16px; 135 | border-radius: 0 4px 4px 0; 136 | cursor: pointer; 137 | font-size: 0.9rem; 138 | } 139 | 140 | .bot-input button:hover { 141 | background-color: #2563eb; 142 | } 143 | 144 | /* Error Message Styling */ 145 | .error-message { 146 | background-color: #fee2e2; 147 | color: #dc2626; 148 | padding: 8px; 149 | margin-bottom: 10px; 150 | border-radius: 4px; 151 | font-size: 0.9rem; 152 | } 153 | 154 | /* Scrollbar Styling for Chatbot */ 155 | .bot-messages::-webkit-scrollbar { 156 | width: 8px; 157 | } 158 | 159 | .bot-messages::-webkit-scrollbar-thumb { 160 | background-color: #3b82f6; 161 | border-radius: 4px; 162 | } 163 | 164 | .bot-messages::-webkit-scrollbar-thumb:hover { 165 | background-color: #2563eb; 166 | } 167 | 168 | /* Chat Message Text */ 169 | .chat-message-text { 170 | font-size: 0.95rem; 171 | line-height: 1.5; 172 | } 173 | 174 | /* Responsive Adjustments */ 175 | @media (max-width: 768px) { 176 | .chatbot-container { 177 | width: 90%; 178 | left: 5%; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 3 | import Home from "./pages/Home"; 4 | import DoctorAuth from "./pages/doctor/auth/page"; 5 | import DoctorDashboard from "./pages/doctor/dashboard/page"; 6 | import PatientAuth from "./pages/patient/auth/page"; 7 | import PatientDashboard from "./pages/patient/dashboard/page"; 8 | import Meet from "./pages/Meet"; 9 | import AdminPage from "./pages/admin/page"; 10 | import AdminAuth from "./pages/admin/auth/page"; 11 | import { useState } from "react"; 12 | import PatientMeet from "./pages/meet/PatientMeet"; 13 | import DoctorMeet from "./pages/meet/DoctorMeet"; 14 | 15 | function App() { 16 | const [callStatus, updateCallStatus] = useState({}); 17 | const [localStream, setLocalStream] = useState(null); 18 | const [remoteStream, setRemoteStream] = useState(null); 19 | const [peerConnection, setPeerConnection] = useState(null); 20 | const [offerData, setOfferData] = useState(null); 21 | const [connectionType, setConnectionType] = useState(null); 22 | 23 | return ( 24 | 25 | 26 | } /> 27 | } /> 28 | } /> 29 | 30 | } /> 31 | } /> 32 | 33 | 50 | } 51 | /> 52 | 69 | } 70 | /> 71 | 72 | } /> 73 | 87 | } 88 | /> 89 | 104 | } 105 | /> 106 | 107 | 108 | ); 109 | } 110 | 111 | export default App; 112 | -------------------------------------------------------------------------------- /client/src/assests/visit.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assests/visit.jpeg -------------------------------------------------------------------------------- /client/src/assets/admin dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/admin dash.png -------------------------------------------------------------------------------- /client/src/assets/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/admin.png -------------------------------------------------------------------------------- /client/src/assets/doctor dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/doctor dash.png -------------------------------------------------------------------------------- /client/src/assets/doctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/doctor.png -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/patient dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/patient dash.png -------------------------------------------------------------------------------- /client/src/assets/patient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/patient.png -------------------------------------------------------------------------------- /client/src/assets/robo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefelx/quickheal/6f300e48609dce45c80e9b303986c6f5d20941e3/client/src/assets/robo.png -------------------------------------------------------------------------------- /client/src/core/APICalls.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | class APICalls { 4 | #server_url = process.env.REACT_APP_SERVER_URL || "http://localhost:5000"; 5 | 6 | async loginDoctor(email, password) { 7 | const response = await axios.post( 8 | `${this.#server_url}/api/v1/auth/doctor/login`, 9 | { 10 | email, 11 | password, 12 | } 13 | ); 14 | 15 | return response; 16 | } 17 | async registerDoctor(name, email, password, specialization) { 18 | const response = await axios.post( 19 | `${this.#server_url}/api/v1/auth/doctor/register`, 20 | { 21 | name, 22 | email, 23 | password, 24 | specialization, 25 | } 26 | ); 27 | 28 | return response; 29 | } 30 | async loginPatient(email, password) { 31 | const response = await axios.post( 32 | `${this.#server_url}/api/v1/auth/patient/login`, 33 | { 34 | email, 35 | password, 36 | } 37 | ); 38 | 39 | return response; 40 | } 41 | async registerPatient(name, email, password, age) { 42 | const response = await axios.post( 43 | `${this.#server_url}/api/v1/auth/patient/register`, 44 | { name, email, password, age } 45 | ); 46 | 47 | return response; 48 | } 49 | 50 | async loginAdmin(email, password) { 51 | const response = await axios.post( 52 | `${this.#server_url}/api/v1/admin/login`, 53 | { 54 | email, 55 | password, 56 | } 57 | ); 58 | 59 | window.localStorage.setItem("adminToken", response.data.token); 60 | 61 | return response; 62 | } 63 | async registerAdmin(name, email, password) { 64 | const response = await axios.post( 65 | `${this.#server_url}/api/v1/admin/register`, 66 | { 67 | headers: { 68 | authorization: `Bearer ${process.env.REACT_APP_PASSCODE}`, 69 | }, 70 | }, 71 | { 72 | name, 73 | email, 74 | password, 75 | } 76 | ); 77 | 78 | return response; 79 | } 80 | async emptyDoctorQueue() { 81 | const response = await axios.post( 82 | `${this.#server_url}/api/v1/admin/empty/doctors`, 83 | { 84 | headers: { 85 | authorization: `Bearer ${window.localStorage.getItem("adminToken")}`, 86 | }, 87 | } 88 | ); 89 | 90 | return response; 91 | } 92 | 93 | async emptyPatientQueue() { 94 | const response = await axios.post( 95 | `${this.#server_url}/api/v1/admin/empty/patients`, 96 | { 97 | headers: { 98 | authorization: `Bearer ${window.localStorage.getItem("adminToken")}`, 99 | }, 100 | } 101 | ); 102 | 103 | return response; 104 | } 105 | } 106 | 107 | const apiCalls = new APICalls(); 108 | 109 | export default apiCalls; 110 | -------------------------------------------------------------------------------- /client/src/core/RTCManager.js: -------------------------------------------------------------------------------- 1 | import { socket } from "../utils/socket"; 2 | 3 | let peerConfiguration = { 4 | iceServers: [ 5 | { 6 | urls: ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"], 7 | }, 8 | ], 9 | }; 10 | 11 | const userType = { 12 | doctor: "doctor", 13 | patient: "patient", 14 | }; 15 | 16 | class RTCManager { 17 | #peerConnection; 18 | #localStream; 19 | #remoteStream; 20 | #didIOffer = false; 21 | #userId; 22 | 23 | getStream() { 24 | return { 25 | localStream: this.#localStream, 26 | remoteStream: this.#remoteStream, 27 | }; 28 | } 29 | 30 | async call() { 31 | try { 32 | console.log("Creating offer"); 33 | const offer = await this.#peerConnection.createOffer(); 34 | 35 | this.#peerConnection.setLocalDescription(offer); 36 | 37 | socket.emit("newOffer", offer); 38 | } catch (error) { 39 | console.log(error); 40 | } 41 | } 42 | 43 | async answerOffer(offerObj) { 44 | await this.#peerConnection.setRemoteDescription(offerObj.offer); 45 | const answer = await this.#peerConnection.createAnswer({}); 46 | await this.#peerConnection.setLocalDescription(answer); 47 | 48 | console.log(offerObj); 49 | console.log(answer); 50 | 51 | offerObj.answer = answer; 52 | 53 | const offerIceCandidates = await socket.emitWithAck("newAnswer", offerObj); 54 | 55 | offerIceCandidates.forEach((c) => { 56 | this.#peerConnection.addIceCandidate(c); 57 | console.log("Added Ice Candidate"); 58 | }); 59 | console.log(offerIceCandidates); 60 | } 61 | 62 | async addAnswer(offerObj) { 63 | await this.#peerConnection.setRemoteDescription(offerObj.answer); 64 | } 65 | 66 | // fetchmedia 67 | fetchMedia() { 68 | return new Promise(async (resolve, reject) => { 69 | try { 70 | const constraints = { 71 | video: true, 72 | audio: true, 73 | }; 74 | this.#localStream = await navigator.mediaDevices.getUserMedia( 75 | constraints 76 | ); 77 | 78 | resolve(this.#localStream); 79 | } catch (error) { 80 | console.log(error); 81 | reject(); 82 | } 83 | }); 84 | } 85 | // create pc 86 | createPeerConnection(userId, didIOffer) { 87 | this.#peerConnection = new RTCPeerConnection(peerConfiguration); 88 | this.#remoteStream = new MediaStream(); 89 | 90 | this.#localStream.getTracks().forEach((track) => { 91 | this.#peerConnection.addTrack(track, this.#localStream); 92 | }); 93 | 94 | this.#peerConnection.addEventListener("signalingstatechange", (e) => { 95 | console.log(this.#peerConnection.signalingState); 96 | }); 97 | 98 | this.#peerConnection.addEventListener("icecandidate", (e) => { 99 | console.log("Ice candidate found.."); 100 | if (e.candidate) { 101 | socket.emit("sendIceCandidateToSignalingServer", { 102 | iceCandidate: e.candidate, 103 | iceUserId: userId, 104 | didIOffer: didIOffer, 105 | }); 106 | } 107 | }); 108 | 109 | this.#peerConnection.addEventListener("track", (e) => { 110 | console.log("Got a track from the other peer!"); 111 | e.streams[0].getTracks().forEach((track) => { 112 | this.#remoteStream.addTrack(track, this.#remoteStream); 113 | }); 114 | }); 115 | 116 | return { 117 | peerConnection: this.#peerConnection, 118 | remoteStream: this.#remoteStream, 119 | }; 120 | } 121 | 122 | async addNewIceCandidate(iceCandidate) { 123 | await this.#peerConnection.addIceCandidate(iceCandidate); 124 | console.log("Added Ice Candidate"); 125 | } 126 | // offer 127 | } 128 | 129 | const rtcmanager = new RTCManager(); 130 | 131 | export default rtcmanager; 132 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 7 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 15 | monospace; 16 | } 17 | /* Reset and base styles */ 18 | * { 19 | box-sizing: border-box; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | body { 25 | margin: 0; 26 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 27 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 28 | sans-serif; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-osx-font-smoothing: grayscale; 31 | line-height: 1.5; 32 | color: #333; 33 | background-color: #f0f4f8; 34 | } 35 | 36 | code { 37 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 38 | monospace; 39 | } 40 | 41 | /* Typography */ 42 | h1, h2, h3, h4, h5, h6 { 43 | margin-bottom: 0.5em; 44 | line-height: 1.2; 45 | } 46 | 47 | /* Utility classes */ 48 | .text-center { text-align: center; } 49 | .text-left { text-align: left; } 50 | .text-right { text-align: right; } 51 | 52 | .mt-1 { margin-top: 0.25rem; } 53 | .mt-2 { margin-top: 0.5rem; } 54 | .mt-4 { margin-top: 1rem; } 55 | 56 | .mb-1 { margin-bottom: 0.25rem; } 57 | .mb-2 { margin-bottom: 0.5rem; } 58 | .mb-4 { margin-bottom: 1rem; } 59 | 60 | .p-1 { padding: 0.25rem; } 61 | .p-2 { padding: 0.5rem; } 62 | .p-4 { padding: 1rem; } 63 | 64 | .rounded { border-radius: 0.25rem; } 65 | .rounded-md { border-radius: 0.375rem; } 66 | .rounded-lg { border-radius: 0.5rem; } 67 | 68 | .shadow { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } 69 | .shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } 70 | 71 | .bg-white { background-color: #ffffff; } 72 | .bg-gray-100 { background-color: #f3f4f6; } 73 | .bg-blue-500 { background-color: #3b82f6; } 74 | 75 | .text-white { color: #ffffff; } 76 | .text-gray-800 { color: #1f2937; } 77 | .text-blue-600 { color: #2563eb; } 78 | 79 | /* Responsive container */ 80 | .container { 81 | width: 100%; 82 | padding-right: 1rem; 83 | padding-left: 1rem; 84 | margin-right: auto; 85 | margin-left: auto; 86 | } 87 | 88 | @media (min-width: 640px) { 89 | .container { 90 | max-width: 640px; 91 | } 92 | } 93 | 94 | @media (min-width: 768px) { 95 | .container { 96 | max-width: 768px; 97 | } 98 | } 99 | 100 | @media (min-width: 1024px) { 101 | .container { 102 | max-width: 1024px; 103 | } 104 | } 105 | 106 | /* Flexbox utilities */ 107 | .flex { display: flex; } 108 | .flex-col { flex-direction: column; } 109 | .items-center { align-items: center; } 110 | .justify-between { justify-content: space-between; } 111 | 112 | /* Form elements */ 113 | input, button { 114 | font-family: inherit; 115 | font-size: inherit; 116 | line-height: inherit; 117 | } 118 | 119 | button { 120 | cursor: pointer; 121 | } 122 | 123 | /* Transitions */ 124 | .transition { 125 | transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; 126 | transition-duration: 300ms; 127 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 128 | } 129 | .container { 130 | width: 100%; 131 | height: 500px; 132 | position: relative; 133 | } 134 | 135 | video { 136 | width: 100%; 137 | height: 100%; 138 | object-fit: cover; 139 | } 140 | 141 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /client/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "../assets/logo.png"; 3 | import FAQSection from "./components/FAQSection"; 4 | import { Link } from "react-router-dom"; 5 | import Bot from "./components/bot"; 6 | 7 | function Home() { 8 | console.log(process.env.REACT_APP_SERVER_URL); 9 | console.log("Home component rendered!"); 10 | 11 | return ( 12 |
13 |
14 |
15 | QuickHeal Logo 20 |
21 | 22 |

23 | Welcome to QuickHeal 24 |

25 | 26 |

27 | Choose your role below to get started. 28 |

29 | 30 |
31 | 35 | Doctor Login 36 | 37 | 41 | Patient Login 42 | 43 |
44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 |
52 |

© 2025 QuickHeal. All rights reserved.

53 |
54 |
55 | 56 | {/* Bot Component */} 57 | 58 |
59 | ); 60 | } 61 | 62 | export default Home; 63 | -------------------------------------------------------------------------------- /client/src/pages/Meet.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import rtcmanager from "../core/RTCManager"; 3 | import { socket } from "../utils/socket"; 4 | 5 | const ConnectionType = { 6 | DOCTOR: "doctor", 7 | PATIENT: "patient", 8 | }; 9 | 10 | function Meet() { 11 | const [message, setMessage] = useState(""); 12 | const localRef = useRef(); 13 | const remoteRef = useRef(); 14 | 15 | useEffect(() => { 16 | socket.on("patient:message", (data) => { 17 | console.log(data); 18 | 19 | setMessage(data.message); 20 | }); 21 | socket.on("doctor:message", (data) => { 22 | console.log(data); 23 | setMessage(data.message); 24 | }); 25 | socket.on("answerResponse", async (offerObj) => { 26 | console.log(offerObj); 27 | await rtcmanager.addAnswer(offerObj); 28 | }); 29 | 30 | socket.on("receivedIceCandidateFromServer", async (iceCandidate) => { 31 | await rtcmanager.addNewIceCandidate(iceCandidate); 32 | console.log(iceCandidate); 33 | }); 34 | }, [socket]); 35 | 36 | useEffect(() => { 37 | const { id, connectionType } = JSON.parse( 38 | window.localStorage.getItem("data") 39 | ); 40 | 41 | (async () => { 42 | if (connectionType === ConnectionType.PATIENT) { 43 | const data = window.localStorage.getItem("data"); 44 | const dataObj = JSON.parse(data); 45 | const { localStream, remoteStream } = await rtcmanager.call(dataObj); 46 | localRef.current.srcObject = localStream; 47 | remoteRef.current.srcObject = remoteStream; 48 | } 49 | })(); 50 | 51 | localRef.current.srcObject = rtcmanager.getStream().localStream; 52 | remoteRef.current.srcObject = rtcmanager.getStream().remoteStream; 53 | }, []); 54 | 55 | return ( 56 |
57 |
58 | {message} 59 |
60 | 61 | 62 |
63 | ); 64 | } 65 | 66 | export default Meet; 67 | -------------------------------------------------------------------------------- /client/src/pages/admin/auth/page.jsx: -------------------------------------------------------------------------------- 1 | // LoginPage.tsx 2 | 3 | import React, { useState } from "react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import apiCalls from "../../../core/APICalls"; 6 | import "animate.css"; 7 | import adminAuthImage from "../../../assets/admin.png"; 8 | 9 | const LoginComponent = ({ setIsLogin }) => { 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const [error, setError] = useState(""); 13 | const navigate = useNavigate(); 14 | 15 | const handleSubmit = async (e) => { 16 | e.preventDefault(); 17 | 18 | // Reset the error state 19 | setError(""); 20 | 21 | try { 22 | const response = await apiCalls.loginAdmin(email, password); 23 | if (response.status === 200) { 24 | console.log(response); 25 | window.localStorage.setItem("token", response.data.token); 26 | 27 | navigate("/admin"); 28 | } 29 | } catch (error) { 30 | setError("Invalid credentials. Please try again."); 31 | } 32 | }; 33 | 34 | return ( 35 |
36 |
37 | {/* Image Section */} 38 |
39 | Admin Auth 44 |
45 | {/* Login Form */} 46 |
47 |

48 | Admin Login 49 |

50 |
51 |
52 | 55 | setEmail(e.target.value)} 59 | required 60 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 61 | /> 62 |
63 |
64 | 67 | setPassword(e.target.value)} 71 | required 72 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 73 | /> 74 |
75 | {error &&
{error}
} 76 | 82 |
83 |

84 | Don't have an account?{" "} 85 | setIsLogin(false)} 88 | > 89 | Register here 90 | 91 |

92 |
93 |
94 |
95 | ); 96 | }; 97 | 98 | const RegisterComponent = ({ setIsLogin }) => { 99 | const [name, setName] = useState(""); 100 | const [email, setEmail] = useState(""); 101 | const [password, setPassword] = useState(""); 102 | const [confirmPassword, setConfirmPassword] = useState(""); 103 | const [error, setError] = useState(""); 104 | 105 | const handleSubmit = async (e) => { 106 | e.preventDefault(); 107 | 108 | // Reset the error state 109 | setError(""); 110 | 111 | // Validate that the passwords match 112 | if (password !== confirmPassword) { 113 | setError("Passwords do not match."); 114 | return; 115 | } 116 | 117 | try { 118 | const response = await apiCalls.registerAdmin(name, email, password); 119 | if (response.status === 201) { 120 | setIsLogin(true); 121 | } 122 | } catch (error) { 123 | setError("An error occurred. Please try again."); 124 | } 125 | }; 126 | 127 | return ( 128 |
129 |
130 | {/* Image Section */} 131 |
132 | Admin Auth 137 |
138 | {/* Registration Form */} 139 |
140 |

141 | Admin Registration 142 |

143 |
144 |
145 | 148 | setName(e.target.value)} 152 | required 153 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 154 | /> 155 |
156 |
157 | 160 | setEmail(e.target.value)} 164 | required 165 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 166 | /> 167 |
168 |
169 | 172 | setPassword(e.target.value)} 176 | required 177 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 178 | /> 179 |
180 |
181 | 184 | setConfirmPassword(e.target.value)} 188 | required 189 | className="w-full p-3 mt-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500" 190 | /> 191 |
192 | {error &&
{error}
} 193 | 199 |
200 |

201 | Already have an account?{" "} 202 | setIsLogin(true)} 205 | > 206 | Login here 207 | 208 |

209 |
210 |
211 |
212 | ); 213 | }; 214 | 215 | const AdminAuth = () => { 216 | const [isLogin, setIsLogin] = useState(true); 217 | 218 | return ( 219 |
220 | {isLogin ? ( 221 | 222 | ) : ( 223 | 224 | )} 225 |
226 | ); 227 | }; 228 | 229 | export default AdminAuth; 230 | -------------------------------------------------------------------------------- /client/src/pages/admin/page.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import apiCalls from "../../core/APICalls"; 4 | import { motion } from "framer-motion"; 5 | import adminDash from "../../assets/admin dash.png"; 6 | 7 | function AdminPage() { 8 | const [loggedIn, setLoggedIn] = useState(null); 9 | 10 | useEffect(() => { 11 | const token = window.localStorage.getItem("token"); 12 | if (token) { 13 | setLoggedIn(token); 14 | } 15 | }, []); 16 | 17 | return ( 18 |
19 |
20 | 26 | Admin Dashboard 27 | 28 | 29 | 37 | 38 | {/* If not logged in, show the login link */} 39 | {!loggedIn ? ( 40 | 46 |

47 | You are not logged in yet. 48 |

49 | 53 | Login 54 | 55 |
56 | ) : ( 57 | 62 |
63 |

64 | Welcome to the Admin Panel 65 |

66 |
67 | {/* Action buttons */} 68 |
69 | 75 | Empty Doctor Queue 76 | 77 | 83 | Empty Patient Queue 84 | 85 |
86 |
87 | )} 88 |
89 |
90 | ); 91 | } 92 | 93 | export default AdminPage; 94 | -------------------------------------------------------------------------------- /client/src/pages/components/ActionButtons.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | CiMicrophoneOff, 3 | CiMicrophoneOn, 4 | CiVideoOff, 5 | CiVideoOn, 6 | } from "react-icons/ci"; 7 | 8 | import { FiVideo, FiVideoOff } from "react-icons/fi"; 9 | import { FaMicrophoneAlt, FaMicrophoneAltSlash } from "react-icons/fa"; 10 | import { MdCallEnd } from "react-icons/md"; 11 | 12 | function ActionButtons({ 13 | callStatus, 14 | localFeedEl, 15 | remoteFeedEl, 16 | updateCallStatus, 17 | localStream, 18 | peerConnection, 19 | setCallEnded, 20 | }) { 21 | function videoHandler() { 22 | const copyCallStatus = { ...callStatus }; 23 | if (copyCallStatus.videoEnabled) { 24 | copyCallStatus.videoEnabled = false; 25 | updateCallStatus(copyCallStatus); 26 | const tracks = localStream.getVideoTracks(); 27 | tracks.forEach((track) => (track.enabled = false)); 28 | } else if (copyCallStatus.videoEnabled === false) { 29 | copyCallStatus.videoEnabled = true; 30 | updateCallStatus(copyCallStatus); 31 | const tracks = localStream.getVideoTracks(); 32 | tracks.forEach((track) => (track.enabled = true)); 33 | } else if (copyCallStatus.videoEnabled === null) { 34 | copyCallStatus.videoEnabled = true; 35 | updateCallStatus(copyCallStatus); 36 | } 37 | } 38 | 39 | function audioHandler() { 40 | const copyCallStatus = { ...callStatus }; 41 | //first, check if the audio is enabled, if so disabled 42 | if (callStatus.audioEnabled === true) { 43 | copyCallStatus.audioEnabled = false; 44 | updateCallStatus(copyCallStatus); 45 | 46 | const tracks = localStream.getAudioTracks(); 47 | tracks.forEach((t) => (t.enabled = false)); 48 | } else if (callStatus.audioEnabled === false) { 49 | copyCallStatus.audioEnabled = true; 50 | updateCallStatus(copyCallStatus); 51 | const tracks = localStream.getAudioTracks(); 52 | tracks.forEach((t) => (t.enabled = true)); 53 | } else { 54 | localStream.getAudioTracks().forEach((t) => { 55 | peerConnection.addTrack(t, localStream); 56 | }); 57 | } 58 | } 59 | 60 | function callEndHandler() { 61 | console.log("Ending call"); 62 | 63 | if (peerConnection) { 64 | const copyStatus = { ...callStatus }; 65 | copyStatus.current = "completed"; 66 | 67 | peerConnection.onicecandidate = null; 68 | peerConnection.ontrack = null; 69 | peerConnection.close(); 70 | peerConnection = null; 71 | localFeedEl.current.srcObject = null; 72 | remoteFeedEl.current.srcObject = null; 73 | localFeedEl = null; 74 | remoteFeedEl = null; 75 | setCallEnded(true); 76 | } 77 | } 78 | 79 | const Button = ({ handler, className, children }) => { 80 | return ( 81 | 87 | ); 88 | }; 89 | 90 | return ( 91 |
92 | 106 | 116 | 117 | 120 |
121 | ); 122 | } 123 | 124 | export default ActionButtons; 125 | -------------------------------------------------------------------------------- /client/src/pages/components/FAQSection.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | function FAQSection() { 4 | const [openFAQ, setOpenFAQ] = useState(null); 5 | 6 | const toggleFAQ = (index) => { 7 | setOpenFAQ(openFAQ === index ? null : index); 8 | }; 9 | 10 | const faqs = [ 11 | { 12 | question: "What is QuickHeal?", 13 | answer: 14 | "QuickHeal is a free healthcare platform where patients can connect with doctors for video consultations, ask questions, and get prescriptions.", 15 | }, 16 | { 17 | question: "How can I use QuickHeal?", 18 | answer: 19 | "Simply sign up, choose your role as a patient or doctor, and get started with the services available.", 20 | }, 21 | { 22 | question: "Is QuickHeal free?", 23 | answer: 24 | "Yes, all services on QuickHeal are completely free for patients and doctors.", 25 | }, 26 | { 27 | question: "Can I book an appointment with a doctor?", 28 | answer: 29 | "No, QuickHeal does not have an appointment booking feature. Instead, you can directly connect with available doctors instantly.", 30 | }, 31 | ]; 32 | 33 | return ( 34 |
35 |
36 |

37 | Frequently Asked Questions (FAQ) 38 |

39 |
40 | {faqs.map((faq, index) => ( 41 |
42 | 49 | {openFAQ === index && ( 50 |

51 | {faq.answer} 52 |

53 | )} 54 |
55 | ))} 56 |
57 |
58 |
59 | ); 60 | } 61 | 62 | export default FAQSection; 63 | -------------------------------------------------------------------------------- /client/src/pages/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loader = ({ size = 'md', color = 'blue' }) => { 4 | const sizeClasses = { 5 | sm: 'w-6 h-6', 6 | md: 'w-8 h-8', 7 | lg: 'w-12 h-12' 8 | }; 9 | 10 | const colorClasses = { 11 | blue: 'border-blue-500', 12 | green: 'border-green-500', 13 | purple: 'border-purple-500', 14 | red: 'border-red-500' 15 | }; 16 | 17 | return ( 18 |
19 |
30 |
Loading...
31 |
32 | ); 33 | }; 34 | 35 | export default Loader; -------------------------------------------------------------------------------- /client/src/pages/components/Video.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Video({ ref }) { 4 | return ; 5 | } 6 | 7 | export default Video; 8 | -------------------------------------------------------------------------------- /client/src/pages/components/WorkingProgress.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function WorkingProgress({ children }) { 4 | return ( 5 |
6 |
7 | Work in Progress 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | 14 | export default WorkingProgress; 15 | -------------------------------------------------------------------------------- /client/src/pages/components/bot.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import MediBot from "./medibot"; 3 | import roboIcon from "../../assets/robo.png"; 4 | import "../../styles/chatbot.css"; 5 | 6 | function Bot() { 7 | const [chatbotOpen, setChatbotOpen] = useState(false); 8 | const [position, setPosition] = useState({ x: 20, y: 20 }); 9 | const [isDragging, setIsDragging] = useState(false); 10 | 11 | const handleChatClick = () => { 12 | if (!isDragging) { 13 | setChatbotOpen((prevState) => !prevState); 14 | } 15 | }; 16 | 17 | const handleMouseDown = () => { 18 | setIsDragging(true); 19 | }; 20 | 21 | const handleMouseUp = () => { 22 | setIsDragging(false); 23 | }; 24 | 25 | const handleMouseMove = (e) => { 26 | if (isDragging) { 27 | setPosition({ 28 | x: window.innerWidth - e.clientX - 50, // Position relative to the right edge 29 | y: window.innerHeight - e.clientY - 50, // Position relative to the bottom edge 30 | }); 31 | } 32 | }; 33 | 34 | useEffect(() => { 35 | document.addEventListener("mousemove", handleMouseMove); 36 | document.addEventListener("mouseup", handleMouseUp); 37 | return () => { 38 | document.removeEventListener("mousemove", handleMouseMove); 39 | document.removeEventListener("mouseup", handleMouseUp); 40 | }; 41 | }, [isDragging]); 42 | 43 | return ( 44 |
53 |
(e.currentTarget.style.transform = "scale(1.1)")} 63 | onMouseLeave={(e) => (e.currentTarget.style.transform = "scale(1)")} 64 | > 65 | Chat Icon 75 |
76 | {chatbotOpen && setChatbotOpen(false)} />} 77 |
78 | ); 79 | } 80 | 81 | export default Bot; 82 | -------------------------------------------------------------------------------- /client/src/pages/components/medibot.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import { 3 | GoogleGenerativeAI, 4 | HarmCategory, 5 | HarmBlockThreshold, 6 | } from "@google/generative-ai"; 7 | 8 | import { FiMaximize2, FiMinimize2, FiMinus } from "react-icons/fi"; 9 | 10 | function MediBot({ onClose, name = "MediBot" }) { 11 | // Default name is "MediBot" 12 | const [messages, setMessages] = useState([]); 13 | const [userInput, setUserInput] = useState(""); 14 | const [chat, setChat] = useState(null); 15 | const [error, setError] = useState(null); 16 | const [isMaximized, setIsMaximized] = useState(false); 17 | 18 | const API_KEY = process.env.REACT_APP_CHATBOT_API_KEY; 19 | 20 | const MODEL_NAME = "gemini-1.0-pro-001"; 21 | 22 | const genAI = new GoogleGenerativeAI(API_KEY); 23 | 24 | const generationConfig = { 25 | temperature: 0.9, 26 | topK: 1, 27 | topP: 1, 28 | maxOutputTokens: 2048, 29 | }; 30 | 31 | const safetySettings = [ 32 | { 33 | category: HarmCategory.HARM_CATEGORY_HARASSMENT, 34 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 35 | }, 36 | { 37 | category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, 38 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 39 | }, 40 | { 41 | category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, 42 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 43 | }, 44 | { 45 | category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, 46 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 47 | }, 48 | ]; 49 | 50 | useEffect(() => { 51 | const initChat = async () => { 52 | try { 53 | if (!API_KEY) { 54 | throw new Error("API key is missing"); 55 | } 56 | console.log("Initializing chat..."); 57 | const model = genAI.getGenerativeModel({ model: MODEL_NAME }); 58 | const newChat = await model.startChat({ 59 | generationConfig, 60 | safetySettings, 61 | history: [], 62 | }); 63 | setChat(newChat); 64 | console.log("Chat initialized successfully"); 65 | } catch (error) { 66 | console.error("Chat initialization error:", error); 67 | setError(`Failed to initialize chat: ${error.message}`); 68 | } 69 | }; 70 | initChat(); 71 | }, []); 72 | 73 | const handleSendMessage = async () => { 74 | try { 75 | const userMessage = { 76 | text: userInput, 77 | role: "user", 78 | timestamp: new Date(), 79 | }; 80 | 81 | setMessages((prevMessages) => [...prevMessages, userMessage]); 82 | setUserInput(""); 83 | 84 | if (chat) { 85 | console.log("Sending message to AI:", userInput); 86 | 87 | const input_prompt = `You are a friendly and knowledgeable medical assistant chatbot named ${name}. Respond to the following message appropriately: ${userInput}`; 88 | const result = await chat.sendMessage(input_prompt); 89 | const botMessage = { 90 | text: result.response.text(), 91 | role: "bot", 92 | timestamp: new Date(), 93 | }; 94 | 95 | setMessages((prevMessages) => [...prevMessages, botMessage]); 96 | } 97 | } catch (error) { 98 | setError("Failed to send message. Please try again."); 99 | } 100 | }; 101 | 102 | const handleKeyPress = (e) => { 103 | if (e.key === "Enter") { 104 | e.preventDefault(); // prevents adding a new line in input field 105 | handleSendMessage(); 106 | } 107 | }; 108 | 109 | const chatContainerRef = useRef(null); 110 | 111 | useEffect(() => { 112 | if (chatContainerRef.current) { 113 | chatContainerRef.current.scrollTop = 114 | chatContainerRef.current.scrollHeight; 115 | } 116 | }, [messages]); 117 | 118 | const toggleMaximize = () => { 119 | setIsMaximized(!isMaximized); 120 | }; 121 | 122 | const chatContainerStyle = isMaximized 123 | ? { 124 | width: "80vw", 125 | height: "80vh", 126 | position: "fixed", 127 | top: "10vh", 128 | left: "10vw", 129 | zIndex: 1001, 130 | } 131 | : { 132 | width: "300px", 133 | height: "400px", 134 | position: "absolute", 135 | bottom: "80px", 136 | right: "0", 137 | }; 138 | 139 | return ( 140 |
153 |
161 |

162 | {name} - Your friend here! 163 |

{" "} 164 | {/* Display the chatbot's name */} 165 |
172 | 184 | 196 |
197 |
198 |
209 | {messages.map((msg, index) => ( 210 |
217 | 228 | {msg.text} 229 | 230 |
231 | ))} 232 | {error && ( 233 |
{error}
234 | )} 235 |
236 |
237 | setUserInput(e.target.value)} 241 | onKeyPress={(e) => e.key === "Enter" && handleSendMessage()} 242 | style={{ 243 | flex: 1, 244 | marginRight: "5px", 245 | padding: "8px", 246 | borderRadius: "20px", 247 | border: "1px solid #ccc", 248 | fontSize: isMaximized ? "16px" : "14px", 249 | }} 250 | placeholder="Type your message..." 251 | /> 252 | 266 |
267 |
268 | ); 269 | } 270 | 271 | export default MediBot; 272 | -------------------------------------------------------------------------------- /client/src/pages/doctor/auth/page.jsx: -------------------------------------------------------------------------------- 1 | // LoginPage.tsx 2 | 3 | import React, { useState } from "react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import apiCalls from "../../../core/APICalls"; 6 | import Loader from "../../components/Loader"; 7 | import doctorImage from "../../../assets/doctor.png"; 8 | import { FaArrowLeft } from "react-icons/fa"; // Add this import 9 | import "animate.css"; 10 | 11 | const LoginComponent = ({ setIsLogin }) => { 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const [error, setError] = useState(""); 15 | const [loader, setLoader] = useState(false); 16 | const navigate = useNavigate(); 17 | 18 | const handleSubmit = async (e) => { 19 | e.preventDefault(); 20 | 21 | // Reset the error state 22 | setError(""); 23 | 24 | try { 25 | setLoader(true); 26 | const response = await apiCalls.loginDoctor(email, password); 27 | if (response.status === 200) { 28 | // Redirect to the doctor's dashboard or home page upon successful login 29 | console.log(response); 30 | window.localStorage.setItem("data", JSON.stringify(response.data.data)); 31 | 32 | navigate("/dashboard/doctor"); 33 | } 34 | } catch (error) { 35 | setError("Invalid credentials. Please try again."); 36 | } finally { 37 | setLoader(false); 38 | } 39 | }; 40 | 41 | return ( 42 |
43 | navigate('/')} 46 | /> 47 | {/* Doctor Image */} 48 |
49 | Doctor 54 |
55 |
56 | {/* Doctor Image for Mobile */} 57 |
58 | Doctor 63 |
64 | 65 |

66 | Doctor Login 67 |

68 | 69 | {/* Login Form */} 70 |
71 |
72 | 75 | setEmail(e.target.value)} 79 | required 80 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 81 | /> 82 |
83 |
84 | 87 | setPassword(e.target.value)} 91 | required 92 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 93 | /> 94 |
95 | {error &&
{error}
} 96 | 110 |
111 |

112 | Don't have an account?{" "} 113 | setIsLogin(false)} 115 | className="text-blue-500 cursor-pointer hover:underline" 116 | > 117 | Register here 118 | 119 |

120 |
121 |
122 | ); 123 | }; 124 | 125 | const RegisterComponent = ({ setIsLogin }) => { 126 | const [name, setName] = useState(""); 127 | const [email, setEmail] = useState(""); 128 | const [password, setPassword] = useState(""); 129 | const [specialization, setSpecialization] = useState(""); 130 | const [confirmPassword, setConfirmPassword] = useState(""); 131 | const [loader, setLoader] = useState(false); 132 | const [error, setError] = useState(""); 133 | const navigate = useNavigate(); // Add this line 134 | 135 | const handleSubmit = async (e) => { 136 | e.preventDefault(); 137 | 138 | // Reset the error state 139 | setError(""); 140 | 141 | // Validate that the passwords match 142 | if (password !== confirmPassword) { 143 | setError("Passwords do not match."); 144 | return; 145 | } 146 | 147 | try { 148 | setLoader(true); 149 | const response = await apiCalls.registerDoctor( 150 | name, 151 | email, 152 | password, 153 | specialization 154 | ); 155 | if (response.status === 201) { 156 | setIsLogin(true); 157 | } 158 | } catch (error) { 159 | setError("An error occurred. Please try again."); 160 | } finally { 161 | setLoader(false); 162 | } 163 | }; 164 | 165 | return ( 166 |
167 | navigate('/')} 170 | /> 171 | {/* Doctor Image */} 172 |
173 | Doctor 178 |
179 |
180 | {/* Doctor Image for Mobile */} 181 |
182 | Doctor 187 |
188 |

189 | Doctor Registration 190 |

191 | {/* Registration Form */} 192 |
193 |
194 | 197 | setName(e.target.value)} 201 | required 202 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 203 | /> 204 |
205 |
206 | 209 | 227 |
228 |
229 | 232 | setEmail(e.target.value)} 236 | required 237 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 238 | /> 239 |
240 |
241 | 244 | setPassword(e.target.value)} 248 | required 249 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 250 | /> 251 |
252 |
253 | 256 | setConfirmPassword(e.target.value)} 260 | required 261 | className="w-full px-4 py-2 mt-1 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" 262 | /> 263 |
264 | {error &&
{error}
} 265 | 279 |
280 |

281 | Already have an account?{" "} 282 | setIsLogin(true)} 284 | className="text-blue-500 cursor-pointer hover:underline" 285 | > 286 | Login here 287 | 288 |

289 |
290 |
291 | ); 292 | }; 293 | 294 | const DoctorAuth = () => { 295 | const [isLogin, setIsLogin] = useState(true); 296 | 297 | return ( 298 |
299 | {isLogin ? ( 300 | 301 | ) : ( 302 | 303 | )} 304 |
305 | ); 306 | }; 307 | 308 | export default DoctorAuth; 309 | -------------------------------------------------------------------------------- /client/src/pages/doctor/dashboard/page.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { socket } from "../../../utils/socket"; 3 | import { useNavigate } from "react-router-dom"; 4 | import rtcmanager from "../../../core/RTCManager"; 5 | import doctorImage from "../../../assets/doctor dash.png"; 6 | import { motion } from "framer-motion"; 7 | import toast, { Toaster } from "react-hot-toast"; 8 | 9 | const Options = { 10 | AVAILABLE: "available", 11 | UNAVAILABLE: "unavailable", 12 | }; 13 | 14 | function DoctorDashboard({ 15 | callStatus, 16 | updateCallStatus, 17 | setLocalStream, 18 | setRemoteStream, 19 | remoteStream, 20 | peerConnection, 21 | setPeerConnection, 22 | offerData, 23 | setOfferData, 24 | connectionType, 25 | setConnectionType, 26 | }) { 27 | const [available, setAvailable] = useState(false); 28 | const [doctor, setDoctor] = useState({}); 29 | const [message, setMessage] = useState(""); 30 | const [answerButton, setAnswerButton] = useState("Accept"); 31 | const [incomingCall, setIncomingCall] = useState([]); 32 | const navigate = useNavigate(); 33 | 34 | // Socket Calls 35 | useEffect(() => { 36 | socket.on("doctor:message", (data) => { 37 | console.log(data); 38 | 39 | setMessage(data.message); 40 | toast(data.message); 41 | }); 42 | 43 | socket.on("newOfferAwaiting", async (offers) => { 44 | console.log(offers); 45 | setIncomingCall(offers); 46 | toast("Incomming Patient 🧑‍🦱"); 47 | }); 48 | }, [socket]); 49 | 50 | // Initial Request 51 | useEffect(() => { 52 | const data = window.localStorage.getItem("data"); 53 | 54 | const dataObj = JSON.parse(data); 55 | setDoctor(dataObj); 56 | setConnectionType(dataObj.connectionType); 57 | 58 | socket.emit("connection-type", { 59 | id: dataObj.id, 60 | connectionType: dataObj.connectionType, 61 | }); 62 | }, []); 63 | 64 | // Sending Availability 65 | useEffect(() => { 66 | socket.emit("doctor:available", { 67 | id: doctor.id, 68 | available, 69 | }); 70 | console.log(available); 71 | }, [available]); 72 | 73 | async function answer(callData) { 74 | setAnswerButton("Allow User Media"); 75 | setOfferData(callData); 76 | const localStream = await rtcmanager.fetchMedia(); 77 | const copyCallStatus = { ...callStatus }; 78 | copyCallStatus.haveMedia = true; 79 | copyCallStatus.videoEnabled = true; 80 | copyCallStatus.audioEnabled = true; 81 | updateCallStatus(copyCallStatus); 82 | setLocalStream(localStream); 83 | 84 | if (callStatus.haveMedia && !peerConnection) { 85 | const { peerConnection, remoteStream } = rtcmanager.createPeerConnection( 86 | doctor.id, 87 | false, 88 | offerData 89 | ); 90 | 91 | setPeerConnection(peerConnection); 92 | setRemoteStream(remoteStream); 93 | } 94 | setAnswerButton("Connecting.."); 95 | setAnswerButton("Connect"); 96 | } 97 | 98 | function logoutHandler() { 99 | socket.emit("doctor:logout", doctor); 100 | window.localStorage.removeItem("data"); 101 | navigate("/"); 102 | } 103 | 104 | useEffect(() => { 105 | if (remoteStream && peerConnection) { 106 | navigate("/meet/doctor"); 107 | } 108 | }, [remoteStream, peerConnection]); 109 | 110 | return ( 111 | 117 |
118 | 119 |
120 | 126 | {/* Image Section without Hover Effect */} 127 | 133 | 140 | 141 | 142 | {/* Header */} 143 |
144 |

Doctor Dashboard

145 | 157 |
158 | 159 | {/* Main Content */} 160 |
161 | 167 | {incomingCall ? ( 168 | incomingCall.map((callData, i) => ( 169 |
170 |

171 | Incoming Patient Call! 172 |

173 | 181 |
182 | )) 183 | ) : ( 184 |

185 | No Incoming Calls 186 |

187 | )} 188 |
189 | 190 | 196 |

Messages

197 |

{message || "No new messages"}

198 |
199 |
200 | 201 | {/* Footer */} 202 |
203 | 209 | Logged in as{" "} 210 | {doctor?.name || "Doctor"} 211 | 216 | Log out 217 | 218 | 219 |
220 |
221 |
222 | ); 223 | } 224 | 225 | export default DoctorDashboard; 226 | -------------------------------------------------------------------------------- /client/src/pages/meet/DoctorMeet.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import rtcmanager from "../../core/RTCManager"; 3 | import { socket } from "../../utils/socket"; 4 | import ActionButtons from "../components/ActionButtons"; 5 | import { Link, useNavigate } from "react-router-dom"; 6 | 7 | const ConnectionType = { 8 | DOCTOR: "doctor", 9 | PATIENT: "patient", 10 | }; 11 | 12 | function DoctorMeet({ 13 | remoteStream, 14 | localStream, 15 | peerConnection, 16 | callStatus, 17 | updateCallStatus, 18 | offerData, 19 | connectionType, 20 | }) { 21 | const [message, setMessage] = useState(""); 22 | 23 | const localRef = useRef(); 24 | const remoteRef = useRef(); 25 | const [answerCreated, setAnswerCreated] = useState(false); 26 | const [callEnded, setCallEnded] = useState(null); 27 | const [returnDashBoard, setReturnDashBoard] = useState(false); 28 | 29 | const navigate = useNavigate(); 30 | 31 | useEffect(() => { 32 | socket.on("doctor:message", (data) => { 33 | console.log(data); 34 | setMessage(data.message); 35 | }); 36 | }, [socket]); 37 | 38 | useEffect(() => { 39 | localRef.current.srcObject = localStream; 40 | remoteRef.current.srcObject = remoteStream; 41 | }, []); 42 | 43 | useEffect(() => { 44 | if (peerConnection) { 45 | peerConnection.ontrack = (e) => { 46 | if (e?.streams?.length) { 47 | setMessage(""); 48 | } else { 49 | setMessage("Disconnected..."); 50 | } 51 | }; 52 | } 53 | }, [peerConnection]); 54 | 55 | useEffect(() => { 56 | const addOfferAndCreateAnswer = async () => { 57 | await rtcmanager.answerOffer(offerData); 58 | setAnswerCreated(true); 59 | }; 60 | 61 | if (!answerCreated && callStatus.haveMedia) { 62 | addOfferAndCreateAnswer(); 63 | } 64 | }, [callStatus.haveMedia, answerCreated]); 65 | 66 | useEffect(() => { 67 | if (callEnded) { 68 | setReturnDashBoard(true); 69 | console.log("inside the if"); 70 | 71 | if (peerConnection) { 72 | const copyStatus = { ...callStatus }; 73 | copyStatus.current = "completed"; 74 | 75 | peerConnection.close(); 76 | peerConnection.onicecandidate = null; 77 | peerConnection.ontrack = null; 78 | peerConnection = null; 79 | updateCallStatus(copyStatus); 80 | localRef.current.srcObject = null; 81 | remoteRef.current.srcObject = null; 82 | } 83 | } 84 | }, [callEnded]); 85 | 86 | if (returnDashBoard) { 87 | return ( 88 |
89 | 90 | 91 | Go to Dashboard 92 | 93 | 94 |
95 | ); 96 | } 97 | 98 | return ( 99 |
100 |
101 | {/* Video layout */} 102 |
103 |
104 | 110 |
111 | Patient's Stream 112 |
113 |
114 | 115 |
116 | 122 |
123 | You 124 |
125 |
126 |
127 |
128 | 129 | 139 | 140 | {/* Error Message */} 141 | {message === "No data found in localStorage" && ( 142 |
143 |

144 | Error: Data not found in localStorage. Please reload and try again. 145 |

146 |
147 | )} 148 |
149 | ); 150 | } 151 | 152 | export default DoctorMeet; 153 | -------------------------------------------------------------------------------- /client/src/pages/meet/PatientMeet.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import rtcmanager from "../../core/RTCManager"; 3 | import { socket } from "../../utils/socket"; 4 | import ActionButtons from "../components/ActionButtons"; 5 | import { Link, useNavigate } from "react-router-dom"; 6 | 7 | const ConnectionType = { 8 | DOCTOR: "doctor", 9 | PATIENT: "patient", 10 | }; 11 | 12 | function PatientMeet({ 13 | remoteStream, 14 | localStream, 15 | peerConnection, 16 | callStatus, 17 | updateCallStatus, 18 | connectionType, 19 | }) { 20 | const [message, setMessage] = useState("Waiting for connection..."); 21 | 22 | const localRef = useRef(); 23 | const remoteRef = useRef(); 24 | 25 | const [callEnded, setCallEnded] = useState(false); 26 | const [returnDashBoard, setReturnDashBoard] = useState(false); 27 | 28 | const navigate = useNavigate(); 29 | 30 | const [requestCreated, setRequestCreated] = useState(false); 31 | 32 | useEffect(() => { 33 | socket.on("patient:message", (data) => { 34 | console.log(data); 35 | 36 | setMessage(data.message); 37 | }); 38 | 39 | socket.on("answerResponse", async (offerObj) => { 40 | console.log(offerObj); 41 | const copyCallStatus = { ...callStatus }; 42 | copyCallStatus.answer = offerObj.answer; 43 | copyCallStatus.myRole = ConnectionType.PATIENT; 44 | updateCallStatus(copyCallStatus); 45 | await rtcmanager.addAnswer(offerObj); 46 | }); 47 | 48 | socket.on("receivedIceCandidateFromServer", async (iceCandidate) => { 49 | await rtcmanager.addNewIceCandidate(iceCandidate); 50 | console.log(iceCandidate); 51 | }); 52 | }, [socket]); 53 | 54 | useEffect(() => { 55 | localRef.current.srcObject = localStream; 56 | remoteRef.current.srcObject = remoteStream; 57 | }, []); 58 | 59 | useEffect(() => { 60 | if (peerConnection) { 61 | peerConnection.ontrack = (e) => { 62 | if (e?.streams?.length) { 63 | setMessage(""); 64 | } else { 65 | setMessage("Disconnected..."); 66 | } 67 | }; 68 | } 69 | }, [peerConnection]); 70 | 71 | useEffect(() => { 72 | console.log("calling"); 73 | 74 | async function makeRequest() { 75 | await rtcmanager.call(); 76 | setRequestCreated(true); 77 | setMessage("Waiting for the Doctor..."); 78 | } 79 | 80 | if (!requestCreated && callStatus.haveMedia) { 81 | makeRequest(); 82 | setMessage(""); 83 | } 84 | }, [callStatus.haveMedia, requestCreated]); 85 | 86 | useEffect(() => { 87 | if (callEnded) { 88 | setReturnDashBoard(true); 89 | if (peerConnection) { 90 | const copyStatus = { ...callStatus }; 91 | copyStatus.current = "completed"; 92 | 93 | peerConnection.close(); 94 | peerConnection.onicecandidate = null; 95 | peerConnection.ontrack = null; 96 | peerConnection = null; 97 | updateCallStatus(copyStatus); 98 | localRef.current.srcObject = null; 99 | remoteRef.current.srcObject = null; 100 | } 101 | } 102 | }, [callEnded]); 103 | 104 | if (returnDashBoard) { 105 | return ( 106 |
107 | 108 | 109 | Go to Dashboard 110 | 111 | 112 |
113 | ); 114 | } 115 | 116 | return ( 117 |
118 |
119 | {/* Waiting message */} 120 | {message && ( 121 |
125 | {message} 126 |
127 | )} 128 | 129 | {/* Video layout */} 130 |
131 |
132 | {/* Video layout */} 133 |
134 |
135 | 141 |
142 | Doctor's Stream 143 |
144 |
145 | 146 |
147 | 153 |
154 | You 155 |
156 |
157 |
158 |
159 |
160 |
161 | 162 | 172 | 173 | {/* Error Message */} 174 | {message === "No data found in localStorage" && ( 175 |
176 |

177 | Error: Data not found in localStorage. Please reload and try again. 178 |

179 |
180 | )} 181 |
182 | ); 183 | } 184 | 185 | export default PatientMeet; 186 | -------------------------------------------------------------------------------- /client/src/styles/chatbot.css: -------------------------------------------------------------------------------- 1 | .chatbot-container { 2 | background-color: white; 3 | border-radius: 8px; 4 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 5 | font-family: Arial, sans-serif; /* Ensure consistent font */ 6 | } 7 | 8 | /* Text themes for flexibility */ 9 | .text-dark_theme { 10 | color: #333; 11 | } 12 | 13 | .text-light_theme { 14 | color: #666; 15 | } 16 | 17 | .bg-dark_theme { 18 | background-color: #4a90e2; 19 | } 20 | 21 | .bg-light_theme { 22 | background-color: #f1f1f1; 23 | } 24 | 25 | /* Input field focus styling */ 26 | input:focus { 27 | outline: none; 28 | border: 1px solid #4a90e2; 29 | } 30 | 31 | /* Bot container */ 32 | .bot-container { 33 | position: fixed; 34 | bottom: 24px; 35 | left: 10px; 36 | height: 70vh; 37 | width: 18.5rem; 38 | background-color: white; 39 | border-radius: 10px 10px 0 10px; 40 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 41 | overflow: hidden; 42 | z-index: 50; 43 | } 44 | 45 | /* Header */ 46 | .bot-header { 47 | background-color: #3b82f6; 48 | color: white; 49 | padding: 10px; 50 | font-size: 1.2rem; 51 | font-weight: bold; 52 | text-align: center; 53 | } 54 | 55 | /* Messages container */ 56 | .bot-messages { 57 | height: calc(100% - 110px); 58 | overflow-y: auto; 59 | padding: 10px; 60 | background-color: #f9fafb; 61 | } 62 | 63 | /* Individual messages */ 64 | .message { 65 | margin-bottom: 10px; 66 | max-width: 80%; 67 | word-wrap: break-word; 68 | } 69 | 70 | .user-message { 71 | background-color: #3b82f6; 72 | color: white; 73 | border-radius: 10px 10px 0 10px; 74 | padding: 8px 12px; 75 | margin-left: auto; 76 | font-size: 0.9rem; /* Adjust font size for readability */ 77 | } 78 | 79 | .bot-message { 80 | background-color: #e5e7eb; 81 | color: #1f2937; 82 | border-radius: 10px 10px 10px 0; 83 | padding: 8px 12px; 84 | margin-right: auto; 85 | font-size: 0.9rem; /* Adjust font size for readability */ 86 | } 87 | 88 | /* Input section */ 89 | .bot-input { 90 | display: flex; 91 | padding: 10px; 92 | background-color: white; 93 | border-top: 1px solid #d1d5db; 94 | } 95 | 96 | .bot-input input { 97 | flex-grow: 1; 98 | padding: 8px; 99 | border: 1px solid #d1d5db; 100 | border-radius: 4px 0 0 4px; 101 | font-size: 0.9rem; 102 | color: #1f2937; /* Dark text for better visibility */ 103 | background-color: #ffffff; /* Ensure clear background */ 104 | } 105 | 106 | .bot-input input::placeholder { 107 | color: #9ca3af; /* Subtle placeholder color */ 108 | } 109 | 110 | .bot-input button { 111 | background-color: #3b82f6; 112 | color: white; 113 | border: none; 114 | padding: 8px 16px; 115 | border-radius: 0 4px 4px 0; 116 | cursor: pointer; 117 | font-size: 0.9rem; 118 | } 119 | 120 | .bot-input button:hover { 121 | background-color: #2563eb; 122 | } 123 | 124 | /* Error message */ 125 | .error-message { 126 | background-color: #fee2e2; 127 | color: #dc2626; 128 | padding: 8px; 129 | margin-bottom: 10px; 130 | border-radius: 4px; 131 | font-size: 0.9rem; 132 | } 133 | 134 | /* Improved scrollbar for the message container */ 135 | .bot-messages::-webkit-scrollbar { 136 | width: 8px; 137 | } 138 | 139 | .bot-messages::-webkit-scrollbar-thumb { 140 | background-color: #3b82f6; 141 | border-radius: 4px; 142 | } 143 | 144 | .bot-messages::-webkit-scrollbar-thumb:hover { 145 | background-color: #2563eb; 146 | } 147 | 148 | /* Adjust text color and visibility */ 149 | .chat-message-text { 150 | font-size: 0.95rem; 151 | line-height: 1.5; 152 | } 153 | -------------------------------------------------------------------------------- /client/src/utils/socket.js: -------------------------------------------------------------------------------- 1 | import { io } from "socket.io-client"; 2 | 3 | const URL = process.env.REACT_APP_SERVER_URL; 4 | 5 | export const socket = io(URL); 6 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx,mdx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /server/.env.local: -------------------------------------------------------------------------------- 1 | PORT=5000 2 | MONGO_URL=your_mongo_connection_string_here 3 | JWT_SECRET=your_jwt_secret_here 4 | SUPERADMIN_PASSCODE=your_superadmin_passcode_here 5 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /server/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDXzCCAkegAwIBAgIGMTQxNzQ5MA0GCSqGSIb3DQEBCwUAMF4xEDAOBgNVBAMT 3 | B1Rlc3QgQ0ExCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYD 4 | VQQHEw1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKEwdUZXN0IENBMB4XDTI0MTEyNzEx 5 | MjIyMloXDTI1MTEyNzExMjIyMlowXjEQMA4GA1UEAxMHVGVzdCBDQTELMAkGA1UE 6 | BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lz 7 | Y28xEDAOBgNVBAoTB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 8 | AoIBAQDGn7PbQi7zmhGRkjugu07qiU6XowfJUJ18GeLoDF4sjMPstAJAIq9akkJ8 9 | NiGNkIOck7E9rlYMdcI5Fs2EZIz5nu94SfitOlgTYg/0G4hbn4BtglpVnzuAE/4m 10 | DgpM8X5uQZiKb4kjI/cnYRMSD22zmqkclHEN/l7dHK56ajRHvUmyRZmE3dzHT685 11 | bQdQRPc1sqBMy9ByQYjCSJdpAiUugv20b8dOgXPS41rn5EIu6CsK/7A6qGWRAxqz 12 | nM+I6XzZOY+hjFtR+Dk6dtxclR9oVzKC0lXEi5f9PTrhRFN5/Xq6fbzzTC+qairH 13 | o+UsDouHfdcq3sC2udhBiUXXvecBAgMBAAGjIzAhMA8GA1UdEwEB/wQFMAMBAf8w 14 | DgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4IBAQDA/HkwKzi2SRBs7f58 15 | Y12jeZutL2rH72ngGrAKEcNtui632sb90ueQlLOoQ06C5i9+BIOcdm2yTN7pRC7J 16 | zS5j+xbIcsHrfqw0uvz361x5fqzsN+UJM/i7lvP/p9rvZOPbnwPCxw12ahLshAW0 17 | j+6bQ40S8T68LAXm1t1Band7L6ebosKDq7uYQ6vo6UHV7JETBoeCqfvxTMLGD8kw 18 | 64lMz2PT0sqshz5NvZKdH8Z8Ltwb8iK+DAVMlIwsLg8dsq+yRPH1L0zYwdwtsC4T 19 | FP79touQy3l8Ik9oqlkFDLcHmWzZ6u3pm65kyD8O0cepwxtM8QJBm6jsNAgynHBs 20 | Thll 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /server/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAxp+z20Iu85oRkZI7oLtO6olOl6MHyVCdfBni6AxeLIzD7LQC 3 | QCKvWpJCfDYhjZCDnJOxPa5WDHXCORbNhGSM+Z7veEn4rTpYE2IP9BuIW5+AbYJa 4 | VZ87gBP+Jg4KTPF+bkGYim+JIyP3J2ETEg9ts5qpHJRxDf5e3Ryuemo0R71JskWZ 5 | hN3cx0+vOW0HUET3NbKgTMvQckGIwkiXaQIlLoL9tG/HToFz0uNa5+RCLugrCv+w 6 | OqhlkQMas5zPiOl82TmPoYxbUfg5OnbcXJUfaFcygtJVxIuX/T064URTef16un28 7 | 80wvqmoqx6PlLA6Lh33XKt7AtrnYQYlF173nAQIDAQABAoIBAB0JifS69zw7wOfE 8 | Nh1oZMAa9LdsaR4AQBW9fxOngzkGFzqKkm2n5HTcPwYDr+IPR35jhRzcWM4XhR5t 9 | e/wn6wgvORkfz1Ab4HZs9qIcSFIEvnTEAsrTrZIq2hPcJ5taDr61rkRGzAisIFzM 10 | b0pLoSEJskFwXhrKnjm7ELLqxdvayXWmtIaIFKAxG6CJgaNMyPcuUOez72R8kZOf 11 | Vu2qS/HOpRDz0sXrGGe4zzOwwe86TjFXIbeoIc9ZCV8oMaIq384Y1rfevYvGobQA 12 | N/1MEgcsTYf09eihDezfNR2Y4I5Uz58EIKogc+YITNWEq64i0IpOoQGT7TdDxu7B 13 | qDzb/cUCgYEA4kdMFENAfnPt+8n16JfxWTMz3Hox75BAgsTEarQPz6tR1Unc8eLx 14 | aFbEwbjc/CM2/x2KHtv/2A17yr0yFZCyGL4H9GulPvJVKO87DcCc4kRpbF40dLjJ 15 | P/cdE2Y02LPBJfVqvYdPCdt5BNSbhVpWOWHgaQp95qJXOQTMTbvtn6MCgYEA4LaB 16 | puPQGsxSSeA+tdnZLmBaejyMYe1OsghJWaYjUkbRGr8k1iza/Au7OvvjZjdmGuqr 17 | 96GRdGJ0MEtzOpZ9SkTpmh9yM6HsZWD1bZOohpX8WBQJltR6NoUDYKpvNi4BLOHU 18 | xsUD6DTzsK6d/5t3UiTIOf25kPhGxmTUFOzceQsCgYEAkeEbODj/PEbrF5fllJVY 19 | gIRVHAAfb23VWTkX1B7PzreJY3NiIQOJVRYTdT0EIv1k+GYH5Ms6jwRqWZNN1b9a 20 | Qj9JC5fWidVbd697p+sUeX9XZAj70oBLJpTexuHukgKZNCqbLSMtnpJC3A1hUkCi 21 | NA5Yyf98mZLxno3sVPUiRfcCgYBTiuam0jlzTRUmzAzkdOpueHoKyenzGlPQko4a 22 | m060bwmcpQWf7qs3W3EvW6hOPzhht29ZsCKwn03NCq/7Tymja/1hRGGj2oZVpnd4 23 | VpYn+ykG4eQyxCnvjQIVdSfFf9MGCauefm6WbOFQhYrOFdGFo5EhJjhQMk21wpSq 24 | duj8wwKBgHW6KovfCxBVehfZUjQuh1qzxSu1Yws6Bgnzg/5e46SrMt6VDOGxuTdI 25 | Wi4+cPq1wQ/KyNaULZcRocuoAM9Aj+13a6l/Pa9iR2oFctGV9TcyiJ5R1YRK27Yx 26 | dVLueUmcvuaFv8XEPB74E3blvuMm9jU6om/dvsUgfRcjztynT1MM 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /server/cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDTDCCAjSgAwIBAgIFNTc0MjgwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH 3 | VGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV 4 | BAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjQxMTI3MTEy 5 | MjI5WhcNMjUxMTI3MTEyMjI5WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0G 6 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdFCqV83hBw1WLEfv8c3DM66XyjigM 7 | lUvtmsvSfaB4hJTd1jdIh3dQAc0PbCkm/ycAB3F6h7M67fGMJIe8cXZV1ahPrW/D 8 | PFYvjjS17mJ2lJNXxTvi+w+NElkb+cg/XMSWib3k9+ddXFCTa6qYOfPYkhnV3HUY 9 | 7PgQ1gIfv9u+An4wkLSQ+ephyxNf0F2oyOw503SpZkSpuLIOMrxDrmRUgYmPaGUz 10 | ODqfsBEVx/1XnjF8E+FQL3CKJT3IX6WOXajmXubo2KJs8oK5XS6dZzdp8wtVlAtd 11 | qNvaRRGT2ZlUJ5QNI/2paOB6jEb5MGzb/P1Jnv6dh7LVl0FNwYZ19e7zAgMBAAGj 12 | WzBZMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 13 | AQUFBwMBBggrBgEFBQcDAjAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ 14 | KoZIhvcNAQELBQADggEBABiYJzSJAFnfPPN1k9FyJ/+SmgFEAnTCfidlRD6Uw9I/ 15 | 0HBoaO20s1CV3ToQ3CApGGa0PlynyRNmOXxX98YST+XWcIsDR/HTf4D2tE7AWAcz 16 | qLh6S4VDTUcJZVq4GCpRLqJJ3SZMQVAk4RaCVJOKn+acYIMG96IB9LybKgzMiPBj 17 | 44FO+UJuAtV4vsTLMAft0NiwClnvFevCZk/lg9Yb82go5c/ieI0kjmwVVXHS2SFd 18 | OZrGGzHUvCdIGjis0YOlHnK9Ar0Pzm4AiHALDuSW47ywVmMrFpm5f07y8Z4BVa+i 19 | T1KDf/bE0DNJGmdQlt5fDSwA7Orf52LDd0hMLDi68rI= 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIDXzCCAkegAwIBAgIGMTQxNzQ5MA0GCSqGSIb3DQEBCwUAMF4xEDAOBgNVBAMT 23 | B1Rlc3QgQ0ExCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYD 24 | VQQHEw1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKEwdUZXN0IENBMB4XDTI0MTEyNzEx 25 | MjIyMloXDTI1MTEyNzExMjIyMlowXjEQMA4GA1UEAxMHVGVzdCBDQTELMAkGA1UE 26 | BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lz 27 | Y28xEDAOBgNVBAoTB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 28 | AoIBAQDGn7PbQi7zmhGRkjugu07qiU6XowfJUJ18GeLoDF4sjMPstAJAIq9akkJ8 29 | NiGNkIOck7E9rlYMdcI5Fs2EZIz5nu94SfitOlgTYg/0G4hbn4BtglpVnzuAE/4m 30 | DgpM8X5uQZiKb4kjI/cnYRMSD22zmqkclHEN/l7dHK56ajRHvUmyRZmE3dzHT685 31 | bQdQRPc1sqBMy9ByQYjCSJdpAiUugv20b8dOgXPS41rn5EIu6CsK/7A6qGWRAxqz 32 | nM+I6XzZOY+hjFtR+Dk6dtxclR9oVzKC0lXEi5f9PTrhRFN5/Xq6fbzzTC+qairH 33 | o+UsDouHfdcq3sC2udhBiUXXvecBAgMBAAGjIzAhMA8GA1UdEwEB/wQFMAMBAf8w 34 | DgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4IBAQDA/HkwKzi2SRBs7f58 35 | Y12jeZutL2rH72ngGrAKEcNtui632sb90ueQlLOoQ06C5i9+BIOcdm2yTN7pRC7J 36 | zS5j+xbIcsHrfqw0uvz361x5fqzsN+UJM/i7lvP/p9rvZOPbnwPCxw12ahLshAW0 37 | j+6bQ40S8T68LAXm1t1Band7L6ebosKDq7uYQ6vo6UHV7JETBoeCqfvxTMLGD8kw 38 | 64lMz2PT0sqshz5NvZKdH8Z8Ltwb8iK+DAVMlIwsLg8dsq+yRPH1L0zYwdwtsC4T 39 | FP79touQy3l8Ik9oqlkFDLcHmWzZ6u3pm65kyD8O0cepwxtM8QJBm6jsNAgynHBs 40 | Thll 41 | -----END CERTIFICATE----- 42 | -------------------------------------------------------------------------------- /server/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAnRQqlfN4QcNVixH7/HNwzOul8o4oDJVL7ZrL0n2geISU3dY3 3 | SId3UAHND2wpJv8nAAdxeoezOu3xjCSHvHF2VdWoT61vwzxWL440te5idpSTV8U7 4 | 4vsPjRJZG/nIP1zElom95PfnXVxQk2uqmDnz2JIZ1dx1GOz4ENYCH7/bvgJ+MJC0 5 | kPnqYcsTX9BdqMjsOdN0qWZEqbiyDjK8Q65kVIGJj2hlMzg6n7ARFcf9V54xfBPh 6 | UC9wiiU9yF+ljl2o5l7m6NiibPKCuV0unWc3afMLVZQLXajb2kURk9mZVCeUDSP9 7 | qWjgeoxG+TBs2/z9SZ7+nYey1ZdBTcGGdfXu8wIDAQABAoIBAC8MQsMeJRdhd+ZL 8 | MTQIqbEfJdnUPWejchYNZt45lY5ze5lEV9OoC5MRrwdfCKZvBxHpqNIgAg11BoPI 9 | z0UgoOaNs7eFxbNvB4t/5wbVC3XkA4vm/gx60UoaREluU82yrgk+4XtKy2g5xcKa 10 | HYMx3HkyLX3SZdpc4+I7D3DOCPXMliXUPITDZEWR8yJyf73IDomqXSTJcxZkfe5j 11 | ITx5qLu4xM3ZUZSeuaJjFrUoFNKV5TutG3Ro5TY4DZiRRj7nUtrvQFcJ2m2aq4du 12 | /m3tC/lT3iisB1YTQOQ7lhT41JmQwacmm7a5liWltEpBfziIGsQoHlqNJn9iMMaA 13 | NLlsZukCgYEAz+4p/Pcq4c2+kogmbNBRWJFk/WG7wGDS3T4m2Hnr5i3meGfWD87X 14 | Jsz/jT+bBzdhfRlT/v5fCkV/Ji28SHHVXBZ2Svn0P88+eh/DPguneFxVlvg+B+Xx 15 | fLWoKTeTuyL7dkb9MLAvgqtCMBRVmlb8JssF/6CwYhnzPnVNah4JkisCgYEAwWR6 16 | /gnZqXW+agBdarIxUUr6CiIDhvrbGAYsvxnKdV5seb8jsjQTX05ph0HAgf6vTbH+ 17 | C4zk+j/pR0T+5F67DExr/+ZVljIzOf5i8EblpytMvy8HhB13n/C71pWAJl9FDIpv 18 | 7TpCNCvfprlFxS8jwmaJkwBDsmhVxBfoXfk9WlkCgYBVHh26HERFoz+8JFf2e2CR 19 | bRN2VifRnNE1GFg2jdJvq6KI75jFZ9rTW1/RppQD5DvYRiIIZIDrZ3+hVV+aGF9L 20 | DzblgmTmKiRdQdte7s5jiwjOgFiESzEL6CEqnkGvfaaKyIyk/bq1Nv4lSG2yfKFE 21 | ECAogNMzlYPpsYM7SYiJeQKBgEViYIIuVIpo9RUg5mM2ZV1OMiFajLtr2xlmdb1m 22 | am3rn62adxsjdePWxSPC4meBUbHlb0kDls92CKMKTQzwM9m4vXLFV+WYQyrGawbb 23 | 2N8+OL3LCL5MuDOI8kuNvbmGqsZSENAi+8euCgLOLAsP25uOoEM+cAtdRZAiJ7s3 24 | hdyZAoGBAIDDqnMLrNNYZG29DzEg6FkG/jcjf4xiovBpaOainXAfSbcxoKsQlhV9 25 | r/v0Np3ar1TisrMakLGtaZVeV7V05kwG+aw9DeXl+hm08YqDZic44bjxcfsruqn7 26 | x0CqMckhgKlar4JxctyNIETB+sfDLO7fFlztYkGCT0rTCTKx56HL 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /server/dist/core/Doctor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Doctor { 4 | constructor(id, name) { 5 | this.id = id; 6 | this.name = name; 7 | this.available = false; 8 | } 9 | getId() { 10 | return this.id; 11 | } 12 | getName() { 13 | return this.name; 14 | } 15 | getAvailability() { 16 | return this.available; 17 | } 18 | setAvailable(isAvailable) { 19 | this.available = isAvailable; 20 | } 21 | } 22 | exports.default = Doctor; 23 | -------------------------------------------------------------------------------- /server/dist/core/PDManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const uid_1 = __importDefault(require("../libs/uid")); 7 | const Session_1 = __importDefault(require("./Session")); 8 | class PDManager { 9 | constructor() { 10 | this.doctors = []; 11 | this.availableDoctors = []; 12 | this.waitingPatients = []; 13 | this.session = []; 14 | this.idSocketMap = {}; 15 | } 16 | static getInstance() { 17 | if (this.instance === null) { 18 | this.instance = new PDManager(); 19 | } 20 | return this.instance; 21 | } 22 | addDoctor(doctor) { 23 | const foundDoctor = this.doctors.find((d) => d.getId() === doctor.getId()); 24 | if (!foundDoctor) { 25 | this.doctors.push(doctor); 26 | } 27 | } 28 | addPatient(patient) { 29 | const foundPatient = this.waitingPatients.find((p) => p.getId() === patient.getId()); 30 | if (!foundPatient) { 31 | this.waitingPatients.push(patient); 32 | } 33 | } 34 | getAvailableDoctors() { 35 | return this.availableDoctors; 36 | } 37 | getDoctors() { 38 | return this.doctors; 39 | } 40 | getUnAvailableDoctors() { 41 | return this.doctors.filter((d) => d.getAvailability() === false); 42 | } 43 | getWaitingPatients() { 44 | return this.waitingPatients.map((patient) => patient.getName()); 45 | } 46 | addToAvailableDoctor(doctor) { 47 | if (!this.availableDoctors.includes(doctor)) { 48 | this.availableDoctors.push(doctor); 49 | } 50 | } 51 | removeFromAvailableDoctor(doctor) { 52 | this.availableDoctors = this.availableDoctors.filter((d) => d.getId() !== doctor.getId()); 53 | } 54 | setDoctorAvailablity(doctorId, available) { 55 | const doctor = this.doctors.find((d) => d.getId() === doctorId); 56 | if (doctor === undefined) { 57 | console.log("Doctor not in the list."); 58 | return; 59 | } 60 | if (available) { 61 | doctor.setAvailable(true); 62 | this.addToAvailableDoctor(doctor); 63 | } 64 | else { 65 | doctor.setAvailable(false); 66 | this.removeFromAvailableDoctor(doctor); 67 | } 68 | } 69 | removeDoctor(id) { 70 | this.doctors = this.doctors.filter((d) => d.getId() !== id); 71 | } 72 | emptyDoctors() { 73 | this.doctors = []; 74 | } 75 | emptyPatient() { 76 | this.waitingPatients = []; 77 | } 78 | mapIdToSocket(doctorId, socketId) { 79 | this.idSocketMap[doctorId] = socketId; 80 | } 81 | getSocketIdFromMap(id) { 82 | return this.idSocketMap[id]; 83 | } 84 | matchConsultation() { 85 | if (this.waitingPatients.length === 0) 86 | return null; 87 | let doctor; 88 | let patient; 89 | if (this.availableDoctors.length > 0) { 90 | doctor = this.availableDoctors[0]; 91 | this.availableDoctors = this.availableDoctors.slice(1); 92 | patient = this.waitingPatients[0]; 93 | this.waitingPatients = this.waitingPatients.slice(1); 94 | const doctorId = doctor.getId(); 95 | const patientId = patient.getId(); 96 | const dockerSocketId = this.idSocketMap[doctorId]; 97 | const patientSocketId = this.idSocketMap[patientId]; 98 | const newSession = new Session_1.default(uid_1.default.generate(10), doctor, patient); 99 | this.session.push(newSession); 100 | return { 101 | doctorId: doctorId, 102 | patientId: patientId, 103 | doctorSocketId: dockerSocketId, 104 | patientSocketId: patientSocketId, 105 | }; 106 | } 107 | return null; 108 | } 109 | endSession(session) { 110 | let doctor = session.getDoctor(); 111 | let sessionId = session.getSessionId(); 112 | this.session = this.session.filter((s) => s.getSessionId() != sessionId); 113 | this.availableDoctors.push(doctor); 114 | } 115 | } 116 | PDManager.instance = null; 117 | exports.default = PDManager; 118 | -------------------------------------------------------------------------------- /server/dist/core/Patient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Patient { 4 | constructor(id, name, description) { 5 | this.id = id; 6 | this.name = name; 7 | this.description = description; 8 | } 9 | getId() { 10 | return this.id; 11 | } 12 | getName() { 13 | return this.name; 14 | } 15 | setName(name) { 16 | this.name = name; 17 | } 18 | getDescription() { 19 | return this.description; 20 | } 21 | setDescription(description) { 22 | this.description = description; 23 | } 24 | } 25 | exports.default = Patient; 26 | -------------------------------------------------------------------------------- /server/dist/core/Session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Session { 4 | constructor(sessionId, doctor, patient) { 5 | this.sessionId = sessionId; 6 | this.doctor = doctor; 7 | this.patient = patient; 8 | this.finished = false; 9 | } 10 | getSessionId() { 11 | return this.sessionId; 12 | } 13 | getDoctor() { 14 | return this.doctor; 15 | } 16 | getPatient() { 17 | return this.patient; 18 | } 19 | finish() { 20 | this.finished = true; 21 | } 22 | isFinished() { 23 | return this.finished; 24 | } 25 | } 26 | exports.default = Session; 27 | -------------------------------------------------------------------------------- /server/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const dotenv_1 = __importDefault(require("dotenv")); 16 | dotenv_1.default.config(); 17 | const http_1 = require("http"); 18 | const express_1 = __importDefault(require("express")); 19 | const socket_io_1 = require("socket.io"); 20 | const cors_1 = __importDefault(require("cors")); 21 | const morgan_1 = __importDefault(require("morgan")); 22 | const connectDB_1 = __importDefault(require("./utils/connectDB")); 23 | const authRouter_1 = __importDefault(require("./router/authRouter")); 24 | const adminRouter_1 = __importDefault(require("./router/adminRouter")); 25 | const Patient_1 = __importDefault(require("./models/Patient")); 26 | const Doctor_1 = __importDefault(require("./models/Doctor")); 27 | const PDManager_1 = __importDefault(require("./core/PDManager")); 28 | const Patient_2 = __importDefault(require("./core/Patient")); 29 | const Doctor_2 = __importDefault(require("./core/Doctor")); 30 | const role_1 = __importDefault(require("./types/role")); 31 | const app = (0, express_1.default)(); 32 | // const key = fs.readFileSync("cert.key"); 33 | // const cert = fs.readFileSync("cert.crt"); 34 | // const server = createServer({ key, cert }, app); 35 | const server = (0, http_1.createServer)(app); 36 | const io = new socket_io_1.Server(server, { 37 | cors: { 38 | origin: process.env.CLIENT_URL || "http://localhost:3000", 39 | methods: ["GET", "POST"], 40 | }, 41 | }); 42 | const manager = PDManager_1.default.getInstance(); 43 | const offers = [ 44 | // offererUserName 45 | // offer 46 | // offerIceCandidates 47 | // answererUserName 48 | // answer 49 | // answererIceCandidates 50 | ]; 51 | // middlewares 52 | app.use(express_1.default.json()); 53 | app.use((0, morgan_1.default)("tiny")); 54 | app.use((0, cors_1.default)({ 55 | origin: process.env.CLIENT_URL || "http://localhost:3000", 56 | })); 57 | app.get("/", (req, res) => { 58 | res.json("hello"); 59 | }); 60 | // Routers 61 | app.use("/api/v1/auth", authRouter_1.default); 62 | app.use("/api/v1/admin", adminRouter_1.default); 63 | // Socket Operations 64 | io.on("connection", (socket) => { 65 | console.log("Client Connected: ", socket.id); 66 | socket.emit("pong"); 67 | socket.on("connection-type", (data) => __awaiter(void 0, void 0, void 0, function* () { 68 | switch (data.connectionType) { 69 | case role_1.default.PATIENT: { 70 | const id = data.id; 71 | let patient = yield Patient_1.default.findById(id) 72 | .select("id") 73 | .select("name") 74 | .select("email") 75 | .select("age"); 76 | if (patient) { 77 | manager.mapIdToSocket(patient.id, socket.id); 78 | manager.addPatient(new Patient_2.default(patient.id, patient.name, data.description)); 79 | console.log(manager.getWaitingPatients()); 80 | socket.emit("patient:message", { 81 | message: "Add to the waiting list", 82 | }); 83 | } 84 | } 85 | case role_1.default.DOCTOR: { 86 | const id = data.id; 87 | let doctor = yield Doctor_1.default.findById(id) 88 | .select("id") 89 | .select("name") 90 | .select("email") 91 | .select("approved") 92 | .select("specialization"); 93 | if (doctor) { 94 | console.log("Doctor Connected!"); 95 | manager.mapIdToSocket(doctor.id, socket.id); 96 | manager.addDoctor(new Doctor_2.default(doctor.id, doctor.name)); 97 | socket.emit("doctor:message", { 98 | message: "Add to the list", 99 | }); 100 | } 101 | else { 102 | socket.emit("doctor:message", { 103 | message: "Not approved to work", 104 | }); 105 | } 106 | console.log(manager.getDoctors()); 107 | } 108 | } 109 | })); 110 | socket.on("doctor:available", (data) => { 111 | const doctorId = data.id; 112 | const available = data.available === "true"; 113 | manager.setDoctorAvailablity(doctorId, available); 114 | console.log(manager.getAvailableDoctors()); 115 | }); 116 | socket.on("newOffer", (newOffer) => { 117 | console.log("Offer Received!"); 118 | const match = manager.matchConsultation(); 119 | console.log(match); 120 | if (match) { 121 | offers.push({ 122 | offererId: match.patientId, 123 | offer: newOffer, 124 | offerIceCandidates: [], 125 | answererId: match.doctorId, 126 | answer: null, 127 | answererIceCandidates: [], 128 | }); 129 | socket 130 | .to(match.doctorSocketId) 131 | .emit("newOfferAwaiting", offers.slice(-1)); 132 | } 133 | }); 134 | socket.on("newAnswer", (offerObj, ackFunction) => { 135 | console.log(offerObj); 136 | const answererSocketId = manager.getSocketIdFromMap(offerObj.offererId); 137 | console.log("Socket ID of Patient: ", answererSocketId); 138 | const offerToUpdate = offers.find((o) => o.offererId === offerObj.offererId); 139 | if (!offerToUpdate) { 140 | console.log("No OfferToUpdate"); 141 | return; 142 | } 143 | ackFunction(offerToUpdate.offerIceCandidates); 144 | offerToUpdate.answer = offerObj.answer; 145 | socket.to(answererSocketId).emit("answerResponse", offerToUpdate); 146 | }); 147 | socket.on("sendIceCandidateToSignalingServer", (iceCandidateObj) => { 148 | const { didIOffer, iceUserId, iceCandidate } = iceCandidateObj; 149 | // console.log(iceCandidate); 150 | if (didIOffer) { 151 | //this ice is coming from the offerer. Send to the answerer 152 | const offerInOffers = offers.find((o) => o.offererId === iceUserId); 153 | if (offerInOffers) { 154 | offerInOffers.offerIceCandidates.push(iceCandidate); 155 | // 1. When the answerer answers, all existing ice candidates are sent 156 | // 2. Any candidates that come in after the offer has been answered, will be passed through 157 | if (offerInOffers.answererId) { 158 | //pass it through to the other socket 159 | const socketToSendTo = manager.getSocketIdFromMap(offerInOffers.answererId); 160 | if (socketToSendTo) { 161 | socket 162 | .to(socketToSendTo) 163 | .emit("receivedIceCandidateFromServer", iceCandidate); 164 | } 165 | else { 166 | console.log("Ice candidate recieved but could not find answere"); 167 | } 168 | } 169 | } 170 | } 171 | else { 172 | //this ice is coming from the answerer. Send to the offerer 173 | //pass it through to the other socket 174 | const offerInOffers = offers.find((o) => o.answererId === iceUserId); 175 | if (offerInOffers) { 176 | const socketToSendTo = manager.getSocketIdFromMap(offerInOffers.offererId); 177 | if (socketToSendTo) { 178 | socket 179 | .to(socketToSendTo) 180 | .emit("receivedIceCandidateFromServer", iceCandidate); 181 | } 182 | else { 183 | console.log("Ice candidate recieved but could not find offerer"); 184 | } 185 | } 186 | } 187 | // console.log(offers) 188 | }); 189 | }); 190 | function startServer() { 191 | // MongoConnection 192 | (0, connectDB_1.default)(); 193 | // Server 194 | server.listen(process.env.PORT || 5000, () => { 195 | console.log("Server Connected!"); 196 | }); 197 | } 198 | startServer(); 199 | -------------------------------------------------------------------------------- /server/dist/libs/uid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class UID { 4 | static generate(length) { 5 | for (let i = 0; i < length; i++) { 6 | const randomIndex = Math.floor(Math.random() * this.characters.length); 7 | this.id += this.characters[randomIndex]; 8 | } 9 | return this.id; 10 | } 11 | } 12 | UID.characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 13 | exports.default = UID; 14 | -------------------------------------------------------------------------------- /server/dist/middleware/superAdminMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.verifyAdmin = exports.superAdminMiddleware = void 0; 16 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); 17 | const Admin_1 = __importDefault(require("../models/Admin")); 18 | const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_key"; 19 | const superAdminMiddleware = (req, res, next) => { 20 | var _a; 21 | const passcode = (_a = req.headers.authorization) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; 22 | if (!passcode) { 23 | res.status(401).json({ message: "Unauthorized." }); 24 | return; 25 | } 26 | try { 27 | if (passcode !== process.env.SUPERADMIN_PASSCODE) { 28 | res.status(403).json({ message: "Forbidden." }); 29 | return; 30 | } 31 | next(); 32 | } 33 | catch (error) { 34 | console.error(error); 35 | res.status(401).json({ message: "Invalid token." }); 36 | } 37 | }; 38 | exports.superAdminMiddleware = superAdminMiddleware; 39 | const verifyAdmin = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 40 | var _a; 41 | const token = (_a = req.headers.authorization) === null || _a === void 0 ? void 0 : _a.split(" ")[1]; 42 | if (!token) { 43 | res.status(401).json({ message: "Unauthorized." }); 44 | return; 45 | } 46 | try { 47 | const decoded = jsonwebtoken_1.default.verify(token, JWT_SECRET); 48 | const adminFound = yield Admin_1.default.findById(decoded.id); 49 | if (!adminFound) { 50 | res.status(403).json({ message: "Forbidden." }); 51 | return; 52 | } 53 | next(); 54 | } 55 | catch (error) { 56 | console.error(error); 57 | res.status(401).json({ message: "Invalid token." }); 58 | } 59 | }); 60 | exports.verifyAdmin = verifyAdmin; 61 | -------------------------------------------------------------------------------- /server/dist/models/Admin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const mongoose_1 = __importDefault(require("mongoose")); 7 | const AdminSchema = new mongoose_1.default.Schema({ 8 | name: { 9 | type: String, 10 | required: true, 11 | trim: true, 12 | }, 13 | email: { 14 | type: String, 15 | required: true, 16 | unique: true, 17 | lowercase: true, 18 | }, 19 | password: { 20 | type: String, 21 | required: true, 22 | }, 23 | }, { 24 | timestamps: true, 25 | }); 26 | // Create the Admin model 27 | const Admin = mongoose_1.default.model("Admin", AdminSchema); 28 | exports.default = Admin; 29 | -------------------------------------------------------------------------------- /server/dist/models/Doctor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || (function () { 19 | var ownKeys = function(o) { 20 | ownKeys = Object.getOwnPropertyNames || function (o) { 21 | var ar = []; 22 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 23 | return ar; 24 | }; 25 | return ownKeys(o); 26 | }; 27 | return function (mod) { 28 | if (mod && mod.__esModule) return mod; 29 | var result = {}; 30 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 31 | __setModuleDefault(result, mod); 32 | return result; 33 | }; 34 | })(); 35 | Object.defineProperty(exports, "__esModule", { value: true }); 36 | const mongoose_1 = __importStar(require("mongoose")); 37 | const DoctorSchema = new mongoose_1.Schema({ 38 | name: { type: String, required: true, trim: true }, 39 | email: { 40 | type: String, 41 | required: true, 42 | unique: true, 43 | lowercase: true, 44 | }, 45 | password: { type: String, required: true, minlength: 8 }, 46 | specialization: { type: String, required: true }, 47 | approved: { type: Boolean, default: false }, 48 | }, { timestamps: true }); 49 | const Doctor = mongoose_1.default.model("Doctor", DoctorSchema); 50 | exports.default = Doctor; 51 | -------------------------------------------------------------------------------- /server/dist/models/Patient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || (function () { 19 | var ownKeys = function(o) { 20 | ownKeys = Object.getOwnPropertyNames || function (o) { 21 | var ar = []; 22 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 23 | return ar; 24 | }; 25 | return ownKeys(o); 26 | }; 27 | return function (mod) { 28 | if (mod && mod.__esModule) return mod; 29 | var result = {}; 30 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 31 | __setModuleDefault(result, mod); 32 | return result; 33 | }; 34 | })(); 35 | Object.defineProperty(exports, "__esModule", { value: true }); 36 | const mongoose_1 = __importStar(require("mongoose")); 37 | const PatientSchema = new mongoose_1.Schema({ 38 | name: { type: String, required: true }, 39 | email: { type: String, required: true, unique: true, lowercase: true }, 40 | password: { type: String, required: true }, 41 | age: { type: Number, required: true, min: 0 }, 42 | }, { 43 | timestamps: true, 44 | }); 45 | const Patient = mongoose_1.default.model("Patient", PatientSchema); 46 | exports.default = Patient; 47 | -------------------------------------------------------------------------------- /server/dist/router/adminRouter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const dotenv_1 = require("dotenv"); 16 | (0, dotenv_1.config)(); 17 | const express_1 = __importDefault(require("express")); 18 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 19 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); 20 | const Admin_1 = __importDefault(require("../models/Admin")); 21 | const Doctor_1 = __importDefault(require("../models/Doctor")); 22 | const superAdminMiddleware_1 = require("../middleware/superAdminMiddleware"); 23 | const PDManager_1 = __importDefault(require("../core/PDManager")); 24 | const router = express_1.default.Router(); 25 | const manager = PDManager_1.default.getInstance(); 26 | // Admin Registration Route 27 | router.post("/register", superAdminMiddleware_1.superAdminMiddleware, (req, res) => __awaiter(void 0, void 0, void 0, function* () { 28 | const { name, email, password } = req.body; 29 | try { 30 | const existingAdmin = yield Admin_1.default.findOne({ email }); 31 | if (existingAdmin) { 32 | res.status(400).json({ message: "Admin already exists." }); 33 | return; 34 | } 35 | const hashedPassword = yield bcryptjs_1.default.hash(password, 10); 36 | const newAdmin = new Admin_1.default({ 37 | name, 38 | email, 39 | password: hashedPassword, 40 | }); 41 | yield newAdmin.save(); 42 | res.status(201).json({ message: "Admin registered successfully." }); 43 | return; 44 | } 45 | catch (error) { 46 | console.error(error); 47 | res.status(500).json({ message: "Internal server error." }); 48 | return; 49 | } 50 | })); 51 | // Admin Login Route 52 | router.post("/login", (req, res) => __awaiter(void 0, void 0, void 0, function* () { 53 | const { email, password } = req.body; 54 | try { 55 | const admin = yield Admin_1.default.findOne({ email }).select("+password"); 56 | if (!admin) { 57 | res.status(404).json({ message: "Admin not found." }); 58 | return; 59 | } 60 | const isPasswordValid = yield bcryptjs_1.default.compare(password, admin.password); 61 | if (!isPasswordValid) { 62 | res.status(401).json({ message: "Invalid credentials." }); 63 | return; 64 | } 65 | const token = jsonwebtoken_1.default.sign({ id: admin._id }, process.env.JWT_SECRET, { 66 | expiresIn: "1h", 67 | }); 68 | res.status(200).json({ token, message: "Login successful." }); 69 | } 70 | catch (error) { 71 | console.error(error); 72 | res.status(500).json({ message: "Internal server error." }); 73 | } 74 | })); 75 | // Approve Doctor Route 76 | router.post("/approve/doctor/:doctorId", superAdminMiddleware_1.verifyAdmin, (req, res) => __awaiter(void 0, void 0, void 0, function* () { 77 | const { doctorId } = req.params; 78 | try { 79 | const doctor = yield Doctor_1.default.findById(doctorId); 80 | if (!doctor) { 81 | res.status(404).json({ message: "Doctor not found." }); 82 | return; 83 | } 84 | doctor.approved = true; 85 | yield doctor.save(); 86 | res.status(200).json({ message: "Doctor approved successfully." }); 87 | } 88 | catch (error) { 89 | console.error(error); 90 | res.status(500).json({ message: "Internal server error." }); 91 | } 92 | })); 93 | router.post("/empty/doctors", superAdminMiddleware_1.verifyAdmin, (req, res) => __awaiter(void 0, void 0, void 0, function* () { 94 | try { 95 | manager.emptyDoctors(); 96 | res.status(200).json({ message: "Doctor's queue is Emptied!" }); 97 | } 98 | catch (error) { 99 | console.error(error); 100 | res.status(500).json({ message: "Internal server error." }); 101 | } 102 | })); 103 | router.post("/empty/patients", superAdminMiddleware_1.verifyAdmin, (req, res) => __awaiter(void 0, void 0, void 0, function* () { 104 | try { 105 | manager.emptyPatient(); 106 | res.status(200).json({ message: "Waiting Patient's queue is Emptied!" }); 107 | } 108 | catch (error) { 109 | console.error(error); 110 | res.status(500).json({ message: "Internal server error." }); 111 | } 112 | })); 113 | exports.default = router; 114 | -------------------------------------------------------------------------------- /server/dist/router/authRouter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const dotenv_1 = require("dotenv"); 16 | (0, dotenv_1.config)(); 17 | const express_1 = __importDefault(require("express")); 18 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 19 | const Doctor_1 = __importDefault(require("../models/Doctor")); 20 | const Patient_1 = __importDefault(require("../models/Patient")); 21 | const role_1 = __importDefault(require("../types/role")); 22 | const router = express_1.default.Router(); 23 | const JWT_SECRET = process.env.JWT_SECRET; 24 | // Doctor Registration 25 | router.post("/doctor/register", (req, res) => __awaiter(void 0, void 0, void 0, function* () { 26 | const { name, email, password, specialization } = req.body; 27 | try { 28 | const hashedPassword = yield bcryptjs_1.default.hash(password, 10); 29 | const doctor = new Doctor_1.default({ 30 | name, 31 | email, 32 | password: hashedPassword, 33 | specialization, 34 | }); 35 | const savedDoctor = yield doctor.save(); 36 | res.status(201).json({ 37 | message: "Doctor registered successfully", 38 | doctor: savedDoctor, 39 | }); 40 | } 41 | catch (err) { 42 | res 43 | .status(500) 44 | .json({ error: "Error registering doctor", details: err.message }); 45 | } 46 | })); 47 | // Patient Registration 48 | router.post("/patient/register", (req, res) => __awaiter(void 0, void 0, void 0, function* () { 49 | const { name, email, password, age } = req.body; 50 | try { 51 | const hashedPassword = yield bcryptjs_1.default.hash(password, 10); 52 | const patient = new Patient_1.default({ 53 | name, 54 | email, 55 | password: hashedPassword, 56 | age, 57 | }); 58 | const savedPatient = yield patient.save(); 59 | res.status(201).json({ 60 | message: "Patient registered successfully", 61 | patient: savedPatient, 62 | }); 63 | } 64 | catch (err) { 65 | res 66 | .status(500) 67 | .json({ error: "Error registering patient", details: err.message }); 68 | } 69 | })); 70 | // Doctor Login 71 | router.post("/doctor/login", (req, res) => __awaiter(void 0, void 0, void 0, function* () { 72 | const { email, password } = req.body; 73 | try { 74 | const doctor = yield Doctor_1.default.findOne({ email }); 75 | if (!doctor) { 76 | res.status(404).json({ error: "Doctor not found" }); 77 | return; 78 | } 79 | const isPasswordValid = yield bcryptjs_1.default.compare(password, doctor.password); 80 | if (!isPasswordValid) { 81 | res.status(401).json({ error: "Invalid password" }); 82 | return; 83 | } 84 | res.json({ 85 | message: "Login successful", 86 | data: { 87 | id: doctor._id, 88 | name: doctor.name, 89 | email: doctor.email, 90 | specialization: doctor.specialization, 91 | approved: doctor.approved, 92 | connectionType: role_1.default.DOCTOR, 93 | }, 94 | }); 95 | return; 96 | } 97 | catch (err) { 98 | res.status(500).json({ error: "Error logging in", details: err.message }); 99 | } 100 | })); 101 | // Patient Login 102 | router.post("/patient/login", (req, res) => __awaiter(void 0, void 0, void 0, function* () { 103 | const { email, password } = req.body; 104 | try { 105 | const patient = yield Patient_1.default.findOne({ email }); 106 | if (!patient) { 107 | res.status(404).json({ error: "Patient not found" }); 108 | return; 109 | } 110 | const isPasswordValid = yield bcryptjs_1.default.compare(password, patient.password); 111 | if (!isPasswordValid) { 112 | res.status(401).json({ error: "Invalid password" }); 113 | return; 114 | } 115 | res.json({ 116 | message: "Login successful", 117 | data: { 118 | id: patient._id, 119 | email: patient.email, 120 | name: patient.name, 121 | age: patient.age, 122 | connectionType: role_1.default.PATIENT, 123 | }, 124 | }); 125 | } 126 | catch (err) { 127 | res.status(500).json({ error: "Error logging in", details: err.message }); 128 | } 129 | })); 130 | exports.default = router; 131 | -------------------------------------------------------------------------------- /server/dist/types/role.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Role; 4 | (function (Role) { 5 | Role["PATIENT"] = "patient"; 6 | Role["DOCTOR"] = "doctor"; 7 | Role["ADMIN"] = "admin"; 8 | })(Role || (Role = {})); 9 | exports.default = Role; 10 | -------------------------------------------------------------------------------- /server/dist/types/tokenDetails.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /server/dist/utils/connectDB.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const mongoose_1 = __importDefault(require("mongoose")); 16 | function connectDB() { 17 | let MONGO_URL = process.env.MONGO_URL || "mongodb://localhost:27017/quickheal"; 18 | return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { 19 | yield mongoose_1.default 20 | .connect(MONGO_URL) 21 | .then(() => { 22 | console.log("MongoDB connected!"); 23 | resolve(); 24 | }) 25 | .catch((e) => { 26 | console.log(e); 27 | reject(); 28 | }); 29 | })); 30 | } 31 | exports.default = connectDB; 32 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "nodemon ./src/index.ts", 7 | "start": "node ./dist/index.js", 8 | "build": "npx tsc" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "@types/bcryptjs": "^2.4.6", 16 | "@types/cors": "^2.8.17", 17 | "@types/dotenv": "^6.1.1", 18 | "@types/jsonwebtoken": "^9.0.7", 19 | "@types/mongoose": "^5.11.96", 20 | "@types/morgan": "^1.9.9", 21 | "bcryptjs": "^2.4.3", 22 | "cors": "^2.8.5", 23 | "dotenv": "^16.4.5", 24 | "express": "^4.21.1", 25 | "express-validator": "^7.2.1", 26 | "jsonwebtoken": "^9.0.2", 27 | "mongoose": "^8.8.3", 28 | "morgan": "^1.10.0", 29 | "socket.io": "^4.8.1", 30 | "typescript": "^5.7.2" 31 | }, 32 | "devDependencies": { 33 | "@types/express": "^5.0.0", 34 | "@types/node": "^22.10.7", 35 | "ts-node": "^10.9.2", 36 | "typescript": "^5.7.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/core/Doctor.ts: -------------------------------------------------------------------------------- 1 | class Doctor { 2 | private id: string; 3 | private name: string; 4 | private available: boolean; 5 | constructor(id: string, name: string) { 6 | this.id = id; 7 | this.name = name; 8 | this.available = false; 9 | } 10 | getId(): string { 11 | return this.id; 12 | } 13 | getName(): string { 14 | return this.name; 15 | } 16 | getAvailability() { 17 | return this.available; 18 | } 19 | setAvailable(isAvailable: boolean): void { 20 | this.available = isAvailable; 21 | } 22 | } 23 | 24 | export default Doctor; 25 | -------------------------------------------------------------------------------- /server/src/core/PDManager.ts: -------------------------------------------------------------------------------- 1 | import UID from "../libs/uid"; 2 | import Doctor from "./Doctor"; 3 | import Patient from "./Patient"; 4 | import Session from "./Session"; 5 | 6 | interface IMatch { 7 | doctorId: string; 8 | patientId: string; 9 | doctorSocketId: string; 10 | patientSocketId: string; 11 | description: string; 12 | } 13 | 14 | interface IWaitingPatient { 15 | patient: Patient; 16 | description: string; 17 | } 18 | 19 | class PDManager { 20 | private static instance: PDManager | null = null; 21 | private doctors: Doctor[] = []; 22 | private patients: Patient[] = []; 23 | private availableDoctors: Doctor[] = []; 24 | private waitingPatients: IWaitingPatient[] = []; 25 | private session: Session[] = []; 26 | private idSocketMap: Record = {}; 27 | 28 | private constructor() {} 29 | 30 | public static getInstance(): PDManager { 31 | if (this.instance === null) { 32 | this.instance = new PDManager(); 33 | } 34 | 35 | return this.instance; 36 | } 37 | 38 | addDoctor(doctor: Doctor): void { 39 | const foundDoctor = this.doctors.find((d) => d.getId() === doctor.getId()); 40 | if (!foundDoctor) { 41 | this.doctors.push(doctor); 42 | } 43 | } 44 | 45 | addPatient(patient: Patient): void { 46 | const foundPatient = this.patients.find( 47 | (p) => p.getId() === patient.getId() 48 | ); 49 | if (!foundPatient) { 50 | this.patients.push(patient); 51 | } 52 | } 53 | 54 | getPatientById(id: string): Patient | null { 55 | const patient = this.patients.find((p) => p.getId() === id); 56 | if (!patient) return null; 57 | return patient; 58 | } 59 | 60 | getAvailableDoctors(): string[] { 61 | return this.availableDoctors.map((d) => d.getName()); 62 | } 63 | 64 | getDoctors(): Doctor[] { 65 | return this.doctors; 66 | } 67 | 68 | getDoctor(id: string) { 69 | return this.doctors.find((p) => p.getId() === id); 70 | } 71 | 72 | getUnAvailableDoctors(): Doctor[] { 73 | return this.doctors.filter((d) => d.getAvailability() === false); 74 | } 75 | 76 | getWaitingPatients(): string[] { 77 | return this.waitingPatients.map((w) => w.patient.getName()); 78 | } 79 | 80 | addToWaitingList(patient: Patient, description: string) { 81 | if (this.waitingPatients.find((w) => w.patient.getId() === patient.getId())) 82 | return; 83 | 84 | this.waitingPatients.push({ 85 | patient, 86 | description, 87 | }); 88 | } 89 | 90 | firstWaitingPatient() { 91 | if (this.waitingPatients.length === 0) return; 92 | 93 | const patient = this.waitingPatients.shift()!; 94 | return patient; 95 | } 96 | 97 | addToAvailableDoctor(doctor: Doctor): void { 98 | if (!this.availableDoctors.includes(doctor)) { 99 | this.availableDoctors.push(doctor); 100 | } 101 | } 102 | 103 | firstDoctorInList() { 104 | if (this.availableDoctors.length === 0) return; 105 | 106 | const doctor = this.availableDoctors.shift()!; 107 | return doctor; 108 | } 109 | 110 | removeFromAvailableDoctor(id: string): void { 111 | this.availableDoctors = this.availableDoctors.filter( 112 | (d) => d.getId() !== id 113 | ); 114 | } 115 | 116 | setDoctorAvailablity(doctorId: string, available: boolean): void { 117 | const doctor = this.doctors.find((d) => d.getId() === doctorId); 118 | 119 | if (doctor === undefined) { 120 | console.log("Doctor not in the list."); 121 | 122 | return; 123 | } 124 | 125 | if (available) { 126 | doctor.setAvailable(true); 127 | this.addToAvailableDoctor(doctor); 128 | } else { 129 | doctor.setAvailable(false); 130 | this.removeFromAvailableDoctor(doctor.getId()); 131 | } 132 | } 133 | 134 | removeDoctor(id: String) { 135 | this.doctors = this.doctors.filter((d) => d.getId() !== id); 136 | } 137 | 138 | removePatient(id: string) { 139 | this.patients = this.patients.filter((p) => p.getId() !== id); 140 | } 141 | 142 | emptyDoctors() { 143 | this.doctors = []; 144 | } 145 | emptyPatient() { 146 | this.waitingPatients = []; 147 | } 148 | 149 | mapIdToSocket(doctorId: string, socketId: string) { 150 | this.idSocketMap[doctorId] = socketId; 151 | } 152 | 153 | getSocketIdFromMap(id: string) { 154 | return this.idSocketMap[id]; 155 | } 156 | 157 | matchConsultation(): IMatch | null { 158 | let doctor = this.firstDoctorInList(); 159 | let waitingPatient = this.firstWaitingPatient(); 160 | 161 | if (waitingPatient && doctor) { 162 | const doctorId = doctor.getId(); 163 | const patientId = waitingPatient.patient.getId(); 164 | 165 | const dockerSocketId = this.idSocketMap[doctorId]; 166 | const patientSocketId = this.idSocketMap[patientId]; 167 | 168 | const newSession = new Session( 169 | UID.generate(10), 170 | doctor, 171 | waitingPatient.patient 172 | ); 173 | this.session.push(newSession); 174 | 175 | return { 176 | doctorId: doctorId, 177 | patientId: patientId, 178 | doctorSocketId: dockerSocketId, 179 | patientSocketId: patientSocketId, 180 | description: waitingPatient.description, 181 | }; 182 | } 183 | 184 | return null; 185 | } 186 | 187 | endSession(session: Session) { 188 | let doctor = session.getDoctor(); 189 | let sessionId = session.getSessionId(); 190 | 191 | this.session = this.session.filter((s) => s.getSessionId() != sessionId); 192 | 193 | this.availableDoctors.push(doctor); 194 | } 195 | } 196 | 197 | export default PDManager; 198 | -------------------------------------------------------------------------------- /server/src/core/Patient.ts: -------------------------------------------------------------------------------- 1 | class Patient { 2 | private id: string; 3 | private name: string; 4 | private description: string; 5 | 6 | constructor(id: string, name: string, description: string) { 7 | this.id = id; 8 | this.name = name; 9 | this.description = description; 10 | } 11 | 12 | public getId(): string { 13 | return this.id; 14 | } 15 | 16 | public getName(): string { 17 | return this.name; 18 | } 19 | 20 | public setName(name: string): void { 21 | this.name = name; 22 | } 23 | 24 | public getDescription(): string { 25 | return this.description; 26 | } 27 | 28 | public setDescription(description: string): void { 29 | this.description = description; 30 | } 31 | } 32 | 33 | export default Patient; 34 | -------------------------------------------------------------------------------- /server/src/core/Session.ts: -------------------------------------------------------------------------------- 1 | import Doctor from "./Doctor"; 2 | import Patient from "./Patient"; 3 | 4 | class Session { 5 | private sessionId: String; 6 | private doctor: Doctor; 7 | private patient: Patient; 8 | private finished: boolean; 9 | 10 | constructor(sessionId: String, doctor: Doctor, patient: Patient) { 11 | this.sessionId = sessionId; 12 | this.doctor = doctor; 13 | this.patient = patient; 14 | this.finished = false; 15 | } 16 | 17 | public getSessionId(): String { 18 | return this.sessionId; 19 | } 20 | 21 | public getDoctor(): Doctor { 22 | return this.doctor; 23 | } 24 | public getPatient(): Patient { 25 | return this.patient; 26 | } 27 | 28 | public finish() { 29 | this.finished = true; 30 | } 31 | 32 | public isFinished(): boolean { 33 | return this.finished; 34 | } 35 | } 36 | 37 | export default Session; 38 | -------------------------------------------------------------------------------- /server/src/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | dotenv.config(); 3 | 4 | import { createServer } from "http"; 5 | import express from "express"; 6 | import { Server } from "socket.io"; 7 | import cors from "cors"; 8 | import morgan from "morgan"; 9 | import fs from "fs"; 10 | 11 | import connectDB from "./utils/connectDB"; 12 | import authRouter from "./router/authRouter"; 13 | import adminRouter from "./router/adminRouter"; 14 | 15 | import PatientDB from "./models/Patient"; 16 | import DoctorDB from "./models/Doctor"; 17 | 18 | import PDManager from "./core/PDManager"; 19 | import Patient from "./core/Patient"; 20 | import Doctor from "./core/Doctor"; 21 | import Role from "./types/role"; 22 | 23 | const app = express(); 24 | 25 | // const key = fs.readFileSync("cert.key"); 26 | // const cert = fs.readFileSync("cert.crt"); 27 | 28 | // const server = createServer({ key, cert }, app); 29 | const server = createServer(app); 30 | const io = new Server(server, { 31 | cors: { 32 | origin: process.env.CLIENT_URL || "http://localhost:3000", 33 | methods: ["GET", "POST"], 34 | }, 35 | }); 36 | const manager = PDManager.getInstance(); 37 | 38 | const offers: { 39 | offererId: string; 40 | offer: any; 41 | offerIceCandidates: any[]; 42 | answererId: any; 43 | answer: any; 44 | answererIceCandidates: any[]; 45 | }[] = [ 46 | // offererUserName 47 | // offer 48 | // offerIceCandidates 49 | // answererUserName 50 | // answer 51 | // answererIceCandidates 52 | ]; 53 | 54 | // middlewares 55 | app.use(express.json()); 56 | app.use(morgan("tiny")); 57 | app.use( 58 | cors({ 59 | origin: process.env.CLIENT_URL || "http://localhost:3000", 60 | }) 61 | ); 62 | 63 | app.get("/", (req, res) => { 64 | res.json("hello"); 65 | }); 66 | 67 | // Routers 68 | app.use("/api/v1/auth", authRouter); 69 | app.use("/api/v1/admin", adminRouter); 70 | 71 | // Socket Operations 72 | io.on("connection", (socket) => { 73 | console.log("Client Connected: ", socket.id); 74 | 75 | socket.emit("pong"); 76 | 77 | socket.on("connection-type", async (data) => { 78 | switch (data.connectionType) { 79 | case Role.PATIENT: { 80 | const id = data.id; 81 | 82 | let patient = await PatientDB.findById(id) 83 | .select("id") 84 | .select("name") 85 | .select("email") 86 | .select("age"); 87 | 88 | if (patient) { 89 | manager.mapIdToSocket(patient.id, socket.id); 90 | manager.addPatient( 91 | new Patient(patient.id, patient.name, data.description) 92 | ); 93 | console.log(manager.getWaitingPatients()); 94 | 95 | socket.emit("patient:message", { 96 | message: "Add to the list", 97 | }); 98 | } 99 | } 100 | 101 | case Role.DOCTOR: { 102 | const id = data.id; 103 | 104 | let doctor = await DoctorDB.findById(id) 105 | .select("id") 106 | .select("name") 107 | .select("email") 108 | .select("approved") 109 | .select("specialization"); 110 | 111 | if (doctor) { 112 | console.log("Doctor Connected!"); 113 | manager.mapIdToSocket(doctor.id, socket.id); 114 | manager.addDoctor(new Doctor(doctor.id, doctor.name)); 115 | socket.emit("doctor:message", { 116 | message: "Added To List", 117 | }); 118 | } else { 119 | socket.emit("doctor:message", { 120 | message: "Not approved to work", 121 | }); 122 | } 123 | 124 | console.log(manager.getDoctors()); 125 | } 126 | } 127 | }); 128 | 129 | socket.on("doctor:available", (data) => { 130 | const doctorId = data.id; 131 | const available: boolean = data.available === "true"; 132 | 133 | console.log(data); 134 | 135 | manager.setDoctorAvailablity(doctorId, available); 136 | console.log(manager.getAvailableDoctors()); 137 | 138 | if (available) { 139 | socket.emit("doctor:message", { message: "You are available ✅" }); 140 | } else { 141 | socket.emit("doctor:message", { message: "You are unavailable ⛔" }); 142 | } 143 | }); 144 | 145 | socket.on("doctor:logout", (data) => { 146 | const { doctor } = data; 147 | 148 | if (!doctor) return; 149 | 150 | manager.removeFromAvailableDoctor(doctor.id); 151 | manager.removeDoctor(doctor.id); 152 | }); 153 | socket.on("patient:logout", (data) => { 154 | const { patient } = data; 155 | 156 | if (!patient) return; 157 | 158 | manager.removePatient(patient.id); 159 | }); 160 | 161 | socket.on("patient:request", (data) => { 162 | const { patient, description } = data; 163 | manager.mapIdToSocket(patient.id, socket.id); 164 | 165 | const p = manager.getPatientById(patient.id); 166 | 167 | if (p) { 168 | manager.addToWaitingList(p, description); 169 | } 170 | 171 | console.log("Waiting List: ", manager.getWaitingPatients()); 172 | 173 | socket.emit("patient:message", { 174 | message: "Add to the Waiting List, waiting for the doctor to answer", 175 | }); 176 | }); 177 | 178 | socket.on("newOffer", (newOffer) => { 179 | console.log("Offer Received!"); 180 | const match = manager.matchConsultation(); 181 | socket.emit("patient:message", "Matching Doctor...."); 182 | console.log(match); 183 | 184 | if (match) { 185 | offers.push({ 186 | offererId: match.patientId, 187 | offer: newOffer, 188 | offerIceCandidates: [], 189 | answererId: match.doctorId, 190 | answer: null, 191 | answererIceCandidates: [], 192 | }); 193 | 194 | socket 195 | .to(match.doctorSocketId) 196 | .emit("newOfferAwaiting", offers.slice(-1)); 197 | 198 | socket.to(match.doctorSocketId).emit("doctor:message", { 199 | message: match.description, 200 | }); 201 | } 202 | }); 203 | 204 | socket.on("newAnswer", (offerObj, ackFunction) => { 205 | console.log(offerObj); 206 | 207 | const answererSocketId = manager.getSocketIdFromMap(offerObj.offererId); 208 | 209 | console.log("Socket ID of Patient: ", answererSocketId); 210 | 211 | const offerToUpdate = offers.find( 212 | (o) => o.offererId === offerObj.offererId 213 | ); 214 | 215 | if (!offerToUpdate) { 216 | console.log("No OfferToUpdate"); 217 | return; 218 | } 219 | 220 | ackFunction(offerToUpdate.offerIceCandidates); 221 | offerToUpdate.answer = offerObj.answer; 222 | 223 | socket.to(answererSocketId).emit("answerResponse", offerToUpdate); 224 | }); 225 | 226 | socket.on("sendIceCandidateToSignalingServer", (iceCandidateObj) => { 227 | const { didIOffer, iceUserId, iceCandidate } = iceCandidateObj; 228 | // console.log(iceCandidate); 229 | if (didIOffer) { 230 | //this ice is coming from the offerer. Send to the answerer 231 | const offerInOffers = offers.find((o) => o.offererId === iceUserId); 232 | if (offerInOffers) { 233 | offerInOffers.offerIceCandidates.push(iceCandidate); 234 | // 1. When the answerer answers, all existing ice candidates are sent 235 | // 2. Any candidates that come in after the offer has been answered, will be passed through 236 | if (offerInOffers.answererId) { 237 | //pass it through to the other socket 238 | const socketToSendTo = manager.getSocketIdFromMap( 239 | offerInOffers.answererId 240 | ); 241 | if (socketToSendTo) { 242 | socket 243 | .to(socketToSendTo) 244 | .emit("receivedIceCandidateFromServer", iceCandidate); 245 | } else { 246 | console.log("Ice candidate recieved but could not find answere"); 247 | } 248 | } 249 | } 250 | } else { 251 | //this ice is coming from the answerer. Send to the offerer 252 | //pass it through to the other socket 253 | const offerInOffers = offers.find((o) => o.answererId === iceUserId); 254 | 255 | if (offerInOffers) { 256 | const socketToSendTo = manager.getSocketIdFromMap( 257 | offerInOffers.offererId 258 | ); 259 | if (socketToSendTo) { 260 | socket 261 | .to(socketToSendTo) 262 | .emit("receivedIceCandidateFromServer", iceCandidate); 263 | } else { 264 | console.log("Ice candidate recieved but could not find offerer"); 265 | } 266 | } 267 | } 268 | // console.log(offers) 269 | }); 270 | }); 271 | 272 | function startServer() { 273 | // MongoConnection 274 | connectDB(); 275 | 276 | // Server 277 | server.listen(process.env.PORT || 5000, () => { 278 | console.log("Server Connected!"); 279 | }); 280 | } 281 | 282 | startServer(); 283 | -------------------------------------------------------------------------------- /server/src/libs/uid.ts: -------------------------------------------------------------------------------- 1 | class UID { 2 | private static characters: String = 3 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 4 | private static id: String; 5 | 6 | public static generate(length: number) { 7 | for (let i = 0; i < length; i++) { 8 | const randomIndex = Math.floor(Math.random() * this.characters.length); 9 | this.id += this.characters[randomIndex]; 10 | } 11 | return this.id; 12 | } 13 | } 14 | 15 | export default UID; 16 | -------------------------------------------------------------------------------- /server/src/middleware/superAdminMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import Admin from "../models/Admin"; 4 | 5 | const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_key"; 6 | 7 | export const superAdminMiddleware = ( 8 | req: Request, 9 | res: Response, 10 | next: NextFunction 11 | ) => { 12 | const passcode = req.headers.authorization?.split(" ")[1]; 13 | 14 | if (!passcode) { 15 | res.status(401).json({ message: "Unauthorized." }); 16 | return; 17 | } 18 | 19 | try { 20 | if (passcode !== process.env.SUPERADMIN_PASSCODE) { 21 | res.status(403).json({ message: "Forbidden." }); 22 | return; 23 | } 24 | 25 | next(); 26 | } catch (error) { 27 | console.error(error); 28 | res.status(401).json({ message: "Invalid token." }); 29 | } 30 | }; 31 | 32 | export const verifyAdmin = async ( 33 | req: Request, 34 | res: Response, 35 | next: NextFunction 36 | ) => { 37 | const token = req.headers.authorization?.split(" ")[1]; 38 | 39 | if (!token) { 40 | res.status(401).json({ message: "Unauthorized." }); 41 | return; 42 | } 43 | 44 | try { 45 | const decoded: any = jwt.verify(token, JWT_SECRET); 46 | const adminFound = await Admin.findById(decoded.id); 47 | if (!adminFound) { 48 | res.status(403).json({ message: "Forbidden." }); 49 | return; 50 | } 51 | 52 | next(); 53 | } catch (error) { 54 | console.error(error); 55 | res.status(401).json({ message: "Invalid token." }); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /server/src/models/Admin.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document, Model } from "mongoose"; 2 | 3 | export interface IAdmin extends Document { 4 | name: string; 5 | email: string; 6 | password: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | 11 | const AdminSchema: Schema = new mongoose.Schema( 12 | { 13 | name: { 14 | type: String, 15 | required: true, 16 | trim: true, 17 | }, 18 | email: { 19 | type: String, 20 | required: true, 21 | unique: true, 22 | lowercase: true, 23 | }, 24 | password: { 25 | type: String, 26 | required: true, 27 | }, 28 | }, 29 | { 30 | timestamps: true, 31 | } 32 | ); 33 | 34 | // Create the Admin model 35 | const Admin: Model = mongoose.model("Admin", AdminSchema); 36 | 37 | export default Admin; 38 | -------------------------------------------------------------------------------- /server/src/models/Doctor.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document, Model } from "mongoose"; 2 | 3 | interface IDoctor extends Document { 4 | name: string; 5 | email: string; 6 | password: string; 7 | specialization: string; 8 | approved: boolean; 9 | createdAt: Date; 10 | updatedAt: Date; 11 | } 12 | 13 | const DoctorSchema: Schema = new Schema( 14 | { 15 | name: { type: String, required: true, trim: true }, 16 | email: { 17 | type: String, 18 | required: true, 19 | unique: true, 20 | lowercase: true, 21 | }, 22 | password: { type: String, required: true, minlength: 8 }, 23 | specialization: { type: String, required: true }, 24 | approved: { type: Boolean, default: false }, 25 | }, 26 | { timestamps: true } 27 | ); 28 | 29 | const Doctor: Model = mongoose.model("Doctor", DoctorSchema); 30 | export default Doctor; 31 | -------------------------------------------------------------------------------- /server/src/models/Patient.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document, Model } from "mongoose"; 2 | 3 | interface IPatient extends Document { 4 | name: string; 5 | email: string; 6 | password: string; 7 | age: number; 8 | createdAt: Date; 9 | updatedAt: Date; 10 | } 11 | 12 | const PatientSchema: Schema = new Schema( 13 | { 14 | name: { type: String, required: true }, 15 | email: { 16 | type: String, 17 | required: true, 18 | unique: true, 19 | match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']}, 20 | password: { type: String, required: true }, 21 | age: { type: Number, required: true, min: 0 }, 22 | }, 23 | { 24 | timestamps: true, 25 | } 26 | ); 27 | 28 | const Patient: Model = mongoose.model( 29 | "Patient", 30 | PatientSchema 31 | ); 32 | 33 | export default Patient; 34 | -------------------------------------------------------------------------------- /server/src/router/adminRouter.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | config(); 3 | import express, { Request, Response } from "express"; 4 | import bcrypt from "bcryptjs"; 5 | import jwt from "jsonwebtoken"; 6 | 7 | import Admin from "../models/Admin"; 8 | import Doctor from "../models/Doctor"; 9 | import { 10 | superAdminMiddleware, 11 | verifyAdmin, 12 | } from "../middleware/superAdminMiddleware"; 13 | import PDManager from "../core/PDManager"; 14 | 15 | const router = express.Router(); 16 | const manager = PDManager.getInstance(); 17 | 18 | // Admin Registration Route 19 | router.post( 20 | "/register", 21 | superAdminMiddleware, 22 | async ( 23 | req: Request< 24 | {}, 25 | {}, 26 | { 27 | name: string; 28 | email: string; 29 | password: string; 30 | }, 31 | {} 32 | >, 33 | res: Response 34 | ) => { 35 | const { name, email, password } = req.body; 36 | 37 | try { 38 | const existingAdmin = await Admin.findOne({ email }); 39 | if (existingAdmin) { 40 | res.status(400).json({ message: "Admin already exists." }); 41 | return; 42 | } 43 | 44 | const hashedPassword = await bcrypt.hash(password, 10); 45 | 46 | const newAdmin = new Admin({ 47 | name, 48 | email, 49 | password: hashedPassword, 50 | }); 51 | 52 | await newAdmin.save(); 53 | res.status(201).json({ message: "Admin registered successfully." }); 54 | return; 55 | } catch (error) { 56 | console.error(error); 57 | res.status(500).json({ message: "Internal server error." }); 58 | return; 59 | } 60 | } 61 | ); 62 | 63 | // Admin Login Route 64 | router.post( 65 | "/login", 66 | async ( 67 | req: Request< 68 | {}, 69 | {}, 70 | { 71 | email: string; 72 | password: string; 73 | }, 74 | {} 75 | >, 76 | res: Response 77 | ) => { 78 | const { email, password } = req.body; 79 | console.log(req.body); 80 | 81 | try { 82 | const admin = await Admin.findOne({ email }).select("+password"); 83 | if (!admin) { 84 | res.status(404).json({ message: "Admin not found." }); 85 | return; 86 | } 87 | 88 | const isPasswordValid = await bcrypt.compare(password, admin.password); 89 | if (!isPasswordValid) { 90 | res.status(401).json({ message: "Invalid credentials." }); 91 | return; 92 | } 93 | 94 | const token = jwt.sign( 95 | { id: admin._id }, 96 | process.env.JWT_SECRET as string, 97 | { 98 | expiresIn: "1h", 99 | } 100 | ); 101 | 102 | res.status(200).json({ token, message: "Login successful." }); 103 | } catch (error) { 104 | console.error(error); 105 | res.status(500).json({ message: "Internal server error." }); 106 | } 107 | } 108 | ); 109 | 110 | // Approve Doctor Route 111 | router.post( 112 | "/approve/doctor/:doctorId", 113 | verifyAdmin, 114 | async (req: Request<{ doctorId: string }, {}, {}, {}>, res: Response) => { 115 | const { doctorId } = req.params; 116 | 117 | try { 118 | const doctor = await Doctor.findById(doctorId); 119 | if (!doctor) { 120 | res.status(404).json({ message: "Doctor not found." }); 121 | return; 122 | } 123 | 124 | doctor.approved = true; 125 | await doctor.save(); 126 | 127 | res.status(200).json({ message: "Doctor approved successfully." }); 128 | } catch (error) { 129 | console.error(error); 130 | res.status(500).json({ message: "Internal server error." }); 131 | } 132 | } 133 | ); 134 | 135 | router.post( 136 | "/empty/doctors", 137 | verifyAdmin, 138 | async (req: Request, res: Response) => { 139 | try { 140 | manager.emptyDoctors(); 141 | res.status(200).json({ message: "Doctor's queue is Emptied!" }); 142 | } catch (error) { 143 | console.error(error); 144 | res.status(500).json({ message: "Internal server error." }); 145 | } 146 | } 147 | ); 148 | router.post( 149 | "/empty/patients", 150 | verifyAdmin, 151 | async (req: Request, res: Response) => { 152 | try { 153 | manager.emptyPatient(); 154 | res.status(200).json({ message: "Waiting Patient's queue is Emptied!" }); 155 | } catch (error) { 156 | console.error(error); 157 | res.status(500).json({ message: "Internal server error." }); 158 | } 159 | } 160 | ); 161 | 162 | export default router; 163 | -------------------------------------------------------------------------------- /server/src/router/authRouter.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | config(); 3 | import express, { Request, Response } from "express"; 4 | import bcrypt from "bcryptjs"; 5 | import jwt from "jsonwebtoken"; 6 | import Doctor from "../models/Doctor"; 7 | import Patient from "../models/Patient"; 8 | import Role from "../types/role"; 9 | import { body, validationResult } from 'express-validator'; 10 | const router = express.Router(); 11 | const JWT_SECRET = process.env.JWT_SECRET as string; 12 | const validateSignup = [ 13 | body('name').notEmpty().withMessage('Name is required'), 14 | body('email') 15 | .isEmail() 16 | .withMessage('Please enter a valid email address') 17 | .normalizeEmail(), 18 | body('password') 19 | .isLength({ min: 6 }) 20 | .withMessage('Password must be at least 6 characters long'), 21 | body('age') 22 | .isInt({ min: 0 }) 23 | .withMessage('Age must be a positive number'), 24 | ]; 25 | interface IDoctorRegister { 26 | name: string; 27 | email: string; 28 | password: string; 29 | specialization: string; 30 | } 31 | interface IPatientRegister { 32 | name: string; 33 | email: string; 34 | password: string; 35 | age: string; 36 | } 37 | interface ILogin { 38 | email: string; 39 | password: string; 40 | } 41 | 42 | // Doctor Registration 43 | router.post( 44 | "/doctor/register", 45 | async (req: Request<{}, {}, IDoctorRegister, null>, res: Response) => { 46 | const { name, email, password, specialization } = req.body; 47 | try { 48 | const hashedPassword = await bcrypt.hash(password, 10); 49 | 50 | const doctor = new Doctor({ 51 | name, 52 | email, 53 | password: hashedPassword, 54 | specialization, 55 | }); 56 | 57 | const savedDoctor = await doctor.save(); 58 | res.status(201).json({ 59 | message: "Doctor registered successfully", 60 | doctor: savedDoctor, 61 | }); 62 | } catch (err: any) { 63 | res 64 | .status(500) 65 | .json({ error: "Error registering doctor", details: err.message }); 66 | } 67 | } 68 | ); 69 | 70 | // Patient Registration 71 | router.post( 72 | "/patient/register", 73 | validateSignup, 74 | async (req: Request<{}, {}, IPatientRegister>, res: Response) => { 75 | const errors = validationResult(req); 76 | if (!errors.isEmpty()) { 77 | res.status(400).json({ errors: errors.array() }); 78 | return; // Explicit return to stop execution 79 | } 80 | 81 | const { name, email, password, age } = req.body; 82 | 83 | try { 84 | const existingPatient = await Patient.findOne({ email }); 85 | if (existingPatient) { 86 | res.status(400).json({ error: "Email already registered" }); 87 | return; 88 | } 89 | 90 | const hashedPassword = await bcrypt.hash(password, 10); 91 | const patient = new Patient({ name, email, password: hashedPassword, age }); 92 | const savedPatient = await patient.save(); 93 | 94 | res.status(201).json({ 95 | message: "Patient registered successfully", 96 | patient: savedPatient, 97 | }); 98 | 99 | } catch (err: any) { 100 | if (err.name === 'ValidationError') { 101 | res.status(400).json({ 102 | error: "Validation Error", 103 | details: Object.values(err.errors).map((e: any) => e.message) 104 | }); 105 | return; 106 | } 107 | 108 | res.status(500).json({ 109 | error: "Error registering patient", 110 | details: err.message 111 | }); 112 | } 113 | } 114 | ); 115 | // Doctor Login 116 | router.post( 117 | "/doctor/login", 118 | async (req: Request<{}, {}, ILogin, null>, res: Response) => { 119 | const { email, password } = req.body; 120 | try { 121 | const doctor = await Doctor.findOne({ email }); 122 | if (!doctor) { 123 | res.status(404).json({ error: "Doctor not found" }); 124 | return; 125 | } 126 | 127 | const isPasswordValid = await bcrypt.compare(password, doctor.password); 128 | if (!isPasswordValid) { 129 | res.status(401).json({ error: "Invalid password" }); 130 | return; 131 | } 132 | 133 | res.json({ 134 | message: "Login successful", 135 | data: { 136 | id: doctor._id, 137 | name: doctor.name, 138 | email: doctor.email, 139 | specialization: doctor.specialization, 140 | approved: doctor.approved, 141 | connectionType: Role.DOCTOR, 142 | }, 143 | }); 144 | return; 145 | } catch (err: any) { 146 | res.status(500).json({ error: "Error logging in", details: err.message }); 147 | } 148 | } 149 | ); 150 | 151 | // Patient Login 152 | router.post( 153 | "/patient/login", 154 | async (req: Request<{}, {}, ILogin, null>, res: Response) => { 155 | const { email, password } = req.body; 156 | try { 157 | const patient = await Patient.findOne({ email }); 158 | 159 | if (!patient) { 160 | res.status(404).json({ error: "Patient not found" }); 161 | return; 162 | } 163 | 164 | const isPasswordValid = await bcrypt.compare(password, patient.password); 165 | if (!isPasswordValid) { 166 | res.status(401).json({ error: "Invalid password" }); 167 | return; 168 | } 169 | 170 | res.json({ 171 | message: "Login successful", 172 | data: { 173 | id: patient._id, 174 | email: patient.email, 175 | name: patient.name, 176 | age: patient.age, 177 | connectionType: Role.PATIENT, 178 | }, 179 | }); 180 | } catch (err: any) { 181 | res.status(500).json({ error: "Error logging in", details: err.message }); 182 | } 183 | } 184 | ); 185 | 186 | export default router; 187 | -------------------------------------------------------------------------------- /server/src/types/role.ts: -------------------------------------------------------------------------------- 1 | enum Role { 2 | PATIENT = "patient", 3 | DOCTOR = "doctor", 4 | ADMIN = "admin", 5 | } 6 | 7 | export default Role; 8 | -------------------------------------------------------------------------------- /server/src/types/tokenDetails.ts: -------------------------------------------------------------------------------- 1 | import { JwtPayload } from "jsonwebtoken"; 2 | 3 | interface IDoctorToken { 4 | doctorId: string; 5 | doctorName: string; 6 | doctorApproved: boolean; 7 | connectionType: string; 8 | iat: number; 9 | exp: number; 10 | } 11 | 12 | interface IPatientToken { 13 | patientId: string; 14 | patientName: string; 15 | patientAge: string; 16 | connectionType: string; 17 | iat: number; 18 | exp: number; 19 | } 20 | 21 | export { IDoctorToken, IPatientToken }; 22 | -------------------------------------------------------------------------------- /server/src/utils/connectDB.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | function connectDB() { 4 | let MONGO_URL = 5 | process.env.MONGO_URL || "mongodb://localhost:27017/quickheal"; 6 | return new Promise(async (resolve, reject) => { 7 | await mongoose 8 | .connect(MONGO_URL) 9 | .then(() => { 10 | console.log("MongoDB connected!"); 11 | resolve(); 12 | }) 13 | .catch((e) => { 14 | console.log(e); 15 | reject(); 16 | }); 17 | }); 18 | } 19 | 20 | export default connectDB; 21 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | "rootDir": "./src" /* Specify the root folder within your source files. */, 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 40 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 41 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 42 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 43 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 44 | // "resolveJsonModule": true, /* Enable importing .json files. */ 45 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 46 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 47 | 48 | /* JavaScript Support */ 49 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 50 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 51 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 52 | 53 | /* Emit */ 54 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 55 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 56 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 57 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "noEmit": true, /* Disable emitting files from a compilation. */ 60 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 61 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 62 | // "removeComments": true, /* Disable emitting comments. */ 63 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 64 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 65 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 80 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 81 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 82 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 83 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 84 | 85 | /* Type Checking */ 86 | "strict": true /* Enable all strict type-checking options. */, 87 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 88 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 89 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 90 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 91 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 92 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 93 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 94 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 95 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 96 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 97 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 98 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 99 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 100 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 101 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 102 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 103 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 104 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 105 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 106 | 107 | /* Completeness */ 108 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 109 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 110 | } 111 | } 112 | --------------------------------------------------------------------------------