├── .gitignore
├── public
├── assets
│ ├── logo.png
│ ├── blood_group.svg
│ ├── height.svg
│ ├── weight.svg
│ └── patient.svg
├── js
│ ├── script.js
│ ├── appointmentActions.js
│ ├── verifyotp.js
│ ├── login.js
│ ├── signup.js
│ ├── bookapp.js
│ ├── editProfile.js
│ └── dashboard.js
├── verifyotp.html
├── dashboard.php
├── login.html
├── ContactUs.html
├── signup.html
├── editProfile.php
├── bookappointment.php
└── userProfile.php
├── composer.json
├── backend
├── user_logout.php
├── includes
│ └── is_loggedin.php
├── doctorapi.php
├── database
│ └── connectDB.php
├── approve_app.php
├── reject_app.php
├── mark_as_completed.php
├── get_user.php
├── ai_api.php
├── verify_email.php
├── user_login.php
├── verify_otp.php
├── profileapi.php
├── book_appointment.php
├── send_otp.php
├── emergencymail.php
└── views
│ ├── doctor_dashboard.php
│ └── patient_dashboard.php
├── README.md
├── package.json
└── eslint.config.mjs
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | db_config.php
3 | vendor/
4 | composer.lock
5 | backend/.env
6 | logs/
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmd-anurag/ca2-project/HEAD/public/assets/logo.png
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "phpmailer/phpmailer": "^6.9",
4 | "vlucas/phpdotenv": "^5.6"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/backend/user_logout.php:
--------------------------------------------------------------------------------
1 | true, "message" => "Logout successful"]);
7 | exit();
8 | ?>
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Problem Statement
2 | "A healthcare assistant providing appointment booking and emergency alerts with an Android UI. It integrates notifications, background scheduling, and secure data storage for seamless patient care."
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@tailwindcss/cli": "^4.0.4",
4 | "tailwindcss": "^4.0.4"
5 | },
6 | "devDependencies": {
7 | "@eslint/js": "^9.20.0",
8 | "eslint": "^9.20.1",
9 | "globals": "^15.15.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import pluginJs from "@eslint/js";
3 |
4 |
5 | /** @type {import('eslint').Linter.Config[]} */
6 | export default [
7 | {files: ["**/*.js"], languageOptions: {sourceType: "script"}},
8 | {languageOptions: { globals: globals.browser }},
9 | pluginJs.configs.recommended,
10 | ];
--------------------------------------------------------------------------------
/public/js/script.js:
--------------------------------------------------------------------------------
1 | const menuBtn = document.getElementById("menu-btn");
2 | const closeBtn = document.getElementById("close-btn");
3 | const mobileMenu = document.getElementById("mobile-menu");
4 |
5 | menuBtn.addEventListener("click", () => {
6 | mobileMenu.classList.remove("-translate-y-full");
7 | });
8 |
9 | closeBtn.addEventListener("click", () => {
10 | mobileMenu.classList.add("-translate-y-full");
11 | });
12 |
--------------------------------------------------------------------------------
/backend/includes/is_loggedin.php:
--------------------------------------------------------------------------------
1 | ';
4 | echo "
You are not logged in. You will be redirected to the login page in a few seconds.
";
5 | echo "";
10 | exit();
11 | }
12 |
--------------------------------------------------------------------------------
/backend/doctorapi.php:
--------------------------------------------------------------------------------
1 | prepare($query);
14 |
15 | if (!$statement) {
16 | echo json_encode(["success" => false, "message" => "Error updating profile"]);
17 | die();
18 | }
19 |
20 | $statement->bind_param("sis", $specialization, $experience_years, $hospital_name);
21 |
22 |
23 | if ($statement->execute()) {
24 | echo json_encode(["success" =>true, "message" => "Profile Updated Successfully"]);
25 | } else {
26 | echo json_encode(["success" => false, "message" => "Error updating profile"]);
27 | }
28 | $statement->close();
29 |
30 | ?>
--------------------------------------------------------------------------------
/backend/database/connectDB.php:
--------------------------------------------------------------------------------
1 | connect_error) {
15 | http_response_code(500);
16 | echo json_encode(["success" => false, "message" => "DB connection failed"]);
17 | die();
18 | }
19 | else {
20 | // Write to custom log file
21 | $log_file = __DIR__ . '/../logs/dblog.log';
22 | $log_message = sprintf(
23 | "[%s] Database Connection: Success\nHost: %s\nDatabase: %s\nUser: %s\n\n",
24 | date('Y-m-d H:i:s'),
25 | $config['DB_HOST'],
26 | $config['DB_NAME'],
27 | $config['DB_USERNAME']
28 | );
29 | error_log($log_message, 3, $log_file);
30 | }
31 | ?>
32 |
--------------------------------------------------------------------------------
/backend/approve_app.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Appointment ID is required."]);
8 | exit();
9 | }
10 |
11 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) {
12 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]);
13 | exit();
14 | }
15 |
16 | $query = "UPDATE appointments SET status='approved' WHERE id=?";
17 | $stmt = $conn->prepare($query);
18 |
19 | $stmt->bind_param("i", $appointment_id);
20 |
21 | if (!$stmt) {
22 | echo json_encode(["success" => false, "message" => "Error approving appointment"]);
23 | die();
24 | }
25 |
26 | if ($stmt->execute()) {
27 | echo json_encode(["success" => true, "message" => "Appointment approved"]);
28 | } else {
29 | echo json_encode(["success" => false, "message" => "Error approving appointment"]);
30 | }
31 |
32 | $stmt->close();
33 | $conn->close();
34 |
35 | ?>
--------------------------------------------------------------------------------
/backend/reject_app.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Appointment ID is required."]);
8 | exit();
9 | }
10 |
11 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) {
12 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]);
13 | exit();
14 | }
15 |
16 | $query = "UPDATE appointments SET STATUS ='rejected' WHERE id=?";
17 | $stmt = $conn->prepare($query);
18 |
19 | $stmt->bind_param("i", $appointment_id);
20 |
21 |
22 | if (!$stmt) {
23 | echo json_encode(["success" => false, "message" => "Error in appointment rejection"]);
24 | die();
25 | }
26 |
27 | if ($stmt->execute()) {
28 | echo json_encode(["success" => true, "message" => "Appointment rejected"]);
29 | } else {
30 | echo json_encode(["success" => false, "message" => "Error in appointment rejection"]);
31 | }
32 |
33 |
34 | $stmt->close();
35 | $conn->close();
36 |
37 | ?>
--------------------------------------------------------------------------------
/backend/mark_as_completed.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Appointment ID is required."]);
12 | exit();
13 | }
14 |
15 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) {
16 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]);
17 | exit();
18 | }
19 |
20 | $query = "UPDATE appointments SET status = 'completed' WHERE id = ?";
21 | $stmt = $conn->prepare($query);
22 |
23 |
24 | if (!$stmt) {
25 | echo json_encode(["success" => false, "message" => "Error preparing the SQL query."]);
26 | exit();
27 | }
28 |
29 | $stmt->bind_param("i", $appointment_id);
30 |
31 | if ($stmt->execute()) {
32 | echo json_encode(["success" => true, "message" => "Appointment marked as complete successfully."]);
33 | } else {
34 | echo json_encode(["success" => false, "message" => "Error executing the SQL query."]);
35 | }
36 |
37 |
38 | $stmt->close();
39 | $conn->close();
40 |
41 | ?>
--------------------------------------------------------------------------------
/backend/get_user.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Invalid email format"];
8 | }
9 |
10 | require __DIR__ . "/database/connectDB.php";
11 |
12 | $query = "SELECT u.name, u.phone, u.address, p.date_of_birth, p.gender, p.medical_history, p.height, p.weight, p.blood_type, p.emergency_contact
13 | FROM users u
14 | LEFT JOIN patients p ON p.id = u.id
15 | WHERE u.email = ?";
16 |
17 | $stmt = $conn->prepare($query);
18 |
19 | if(!$stmt) {
20 | return ["success" => false, "message" => "Internal Server Error: " . $conn->error];
21 | }
22 |
23 | $stmt->bind_param('s', $email);
24 |
25 | if(!$stmt->execute()) {
26 | return ["success" => false, "message" => "Internal Server Error: " . $stmt->error];
27 | }
28 |
29 | $result = $stmt->get_result()->fetch_assoc();
30 | $stmt->close();
31 |
32 | return ["success" => true, "data" => $result];
33 | }
34 |
35 | // Handle direct API calls via POST
36 | if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST["email"])) {
37 | $email = $_POST["email"];
38 | $result = getUserData($email);
39 |
40 | if (!$result["success"]) {
41 | http_response_code(500);
42 | }
43 |
44 | echo json_encode($result);
45 | }
46 | ?>
--------------------------------------------------------------------------------
/backend/ai_api.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Bad Request Method"]);
6 | die();
7 | }
8 | if(!isset($_POST["userinput"])) {
9 | http_response_code(400);
10 | echo json_encode(["sucess" => false, "message" => "missing body"]);
11 | die();
12 | }
13 |
14 | // LOAD FROM .ENV
15 | require '../vendor/autoload.php';
16 | $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
17 | $dotenv->load();
18 | $apikey = $_ENV["API_KEY"];
19 |
20 |
21 | $userInput = $_POST["userinput"];
22 |
23 |
24 | // Gemini api's json structure is nasty bruh
25 | $requestBody = json_encode([
26 | "contents" => [
27 | [
28 | "role" => "user",
29 | "parts" => [["text" => "You are a helpful healthcare assistant. Avoid very long responses. Do not format your responses and answer in plaintext."]]
30 | ],
31 | [
32 | "role" => "user",
33 | "parts" => [["text" => $userInput]]
34 | ]
35 | ]
36 | ]);
37 |
38 |
39 | $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$apikey";
40 | $ch = curl_init();
41 |
42 | curl_setopt($ch, CURLOPT_URL, $url);
43 | curl_setopt($ch, CURLOPT_POST, 1);
44 | curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
45 | curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
46 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
47 |
48 | $response = curl_exec($ch);
49 | curl_close($ch);
50 |
51 |
52 | echo $response;
--------------------------------------------------------------------------------
/backend/verify_email.php:
--------------------------------------------------------------------------------
1 | false, "message" => "DB Error"]);
17 | die();
18 | }
19 |
20 | if (!isset($_POST["email"])) {
21 | http_response_code(400);
22 | echo json_encode(["success" => false, "message" => "No email provided."]);
23 | die();
24 | }
25 |
26 | try {
27 | // Sanitize the email input
28 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL);
29 |
30 | $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
31 | if (!$stmt) {
32 | http_response_code(500);
33 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]);
34 | die();
35 | }
36 |
37 | $stmt->bind_param("s", $email);
38 | $stmt->execute();
39 | $result = $stmt->get_result();
40 |
41 | if ($result && $result->num_rows > 0) {
42 | echo json_encode(["success" => false, "message" => "Email already exists"]);
43 | } else {
44 | echo json_encode(["success" => true, "message" => "Email is available"]);
45 | }
46 |
47 | $stmt->close();
48 | } catch(Exception $error) {
49 | http_response_code(500);
50 | echo json_encode(["success" => false, "message" => $error->getMessage()]);
51 | }
52 | ?>
53 |
--------------------------------------------------------------------------------
/public/assets/blood_group.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/backend/user_login.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Bad Request Method"]);
13 | die();
14 | }
15 |
16 | if (!isset($_POST["email"]) || !isset($_POST["password"])) {
17 | http_response_code(400);
18 | echo json_encode(["success" => false, "message" => "Missing credentials"]);
19 | die();
20 | }
21 |
22 | require "./database/connectDB.php";
23 |
24 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL);
25 |
26 | $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
27 | if (!$stmt) {
28 | http_response_code(500);
29 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]);
30 | die();
31 | }
32 |
33 | $stmt->bind_param("s", $email);
34 | $stmt->execute();
35 | $result = $stmt->get_result();
36 |
37 | if (!$result) {
38 | http_response_code(500);
39 | echo json_encode(["success" => false, "message" => "Database query failed"]);
40 | die();
41 | }
42 |
43 | if ($result->num_rows === 0) {
44 | http_response_code(400);
45 | echo json_encode(["success" => false, "message" => "User does not exist."]);
46 | die();
47 | }
48 |
49 | $row = $result->fetch_assoc();
50 | $entered_password = $_POST["password"];
51 | $hashed_password = $row["password"];
52 |
53 | if (password_verify($entered_password, $hashed_password)) {
54 | $_SESSION["user"] = ["email" => $email];
55 | echo json_encode(["success" => true, "message" => "Login Successful"]);
56 | } else {
57 | http_response_code(401);
58 | echo json_encode(["success" => false, "message" => "Incorrect Password"]);
59 | }
60 |
61 | $stmt->close();
62 | } catch (Exception $e) {
63 | http_response_code(500);
64 | echo json_encode(["success" => false, "message" => $e->getMessage()]);
65 | }
66 | ?>
67 |
--------------------------------------------------------------------------------
/public/js/appointmentActions.js:
--------------------------------------------------------------------------------
1 | document.querySelectorAll(".approve-btn").forEach((button) => {
2 | button.addEventListener("click", ()=> {
3 | if(button.hasAttribute('disabled')) return;
4 |
5 | const appointmentId = button.getAttribute('data-appointment-id');
6 | updateAppointmentStatus(appointmentId, 'approved');
7 | })
8 | })
9 |
10 | document.querySelectorAll(".reject-btn").forEach((button) => {
11 | button.addEventListener("click", () => {
12 | if(button.hasAttribute('disabled')) return;
13 |
14 | const appointmentId = button.getAttribute('data-appointment-id');
15 | updateAppointmentStatus(appointmentId, 'rejected');
16 | })
17 | })
18 |
19 | document.querySelectorAll(".complete-btn").forEach((button) => {
20 | button.addEventListener('click', () => {
21 | if(button.hasAttribute('disabled')) return;
22 |
23 | const appointmentId = button.getAttribute('data-appointment-id');
24 | updateAppointmentStatus(appointmentId, 'completed');
25 | })
26 | })
27 |
28 | let loader = document.getElementById('loader-element');
29 |
30 | const updateAppointmentStatus = async (id, status) => {
31 |
32 | let url = "";
33 |
34 | if(status === "approved") {
35 | url = "http://localhost/ca2-project/backend/approve_app.php";
36 | }
37 | else if(status === "rejected") {
38 | url = "http://localhost/ca2-project/backend/reject_app.php";
39 | }
40 | else if(status === "completed") {
41 | url = "http://localhost/ca2-project/backend/mark_as_completed.php";
42 | }
43 |
44 | const formData = new FormData();
45 | formData.append("appointment_id", id);
46 |
47 | try {
48 | loader.classList.remove('hidden');
49 |
50 | let response = await fetch(url, {
51 | method: 'POST',
52 | credentials: 'include',
53 | body: formData
54 | });
55 |
56 | let result = await response.json();
57 |
58 | if(result.success) {
59 | window.location.reload();
60 | }
61 | else {
62 | alert(result.message);
63 | }
64 | }
65 | catch(e) {
66 | console.log(e);
67 | }
68 | finally {
69 | loader.classList.add('hidden');
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/public/js/verifyotp.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | document.getElementById("otpinp1").focus();
3 | }
4 |
5 | const form = document.getElementById("otp-form");
6 |
7 |
8 | // Event listener for filling
9 | form.addEventListener("input", function(event) {
10 | const otpinputs = [...form.getElementsByClassName("otp-input")];
11 | const currentInput = event.target;
12 | const currentIndex = otpinputs.indexOf(currentInput);
13 |
14 | if(currentIndex !== -1 && currentInput.value.length > 0) {
15 | const nextInput = otpinputs[currentIndex + 1];
16 | if(nextInput) {
17 | nextInput.focus();
18 | }
19 | }
20 | })
21 |
22 | // Event listener for deleting
23 | form.addEventListener("keydown", function(event) {
24 | if(event.key == "Backspace") {
25 | const otpinputs = [...form.getElementsByClassName("otp-input")];
26 | const currentInput = event.target;
27 | const currentIndex = otpinputs.indexOf(currentInput);
28 |
29 | if(currentIndex > 0 && currentInput.value == "") {
30 | otpinputs[currentIndex-1].focus();
31 | }
32 | }
33 | })
34 |
35 | let verifybtn = document.getElementById("verifybtn");
36 | verifybtn.addEventListener("click", async () => {
37 |
38 | const otpinputs = [...form.getElementsByClassName("otp-input")];
39 |
40 | for (let i = 0; i < otpinputs.length; i++) {
41 | const element = otpinputs[i];
42 | if (element.value === "") {
43 | alert("Incomplete OTP");
44 | return;
45 | }
46 | }
47 |
48 | const formdata = new FormData();
49 |
50 | for (let i = 0; i < otpinputs.length; ++i) {
51 | const element = otpinputs[i];
52 |
53 | formdata.append(i , element.value);
54 | }
55 |
56 | let response;
57 | try {
58 | response = await fetch("http://localhost/ca2-project/backend/verify_otp.php", {
59 | method: "POST",
60 | credentials: "include",
61 | body: formdata
62 | })
63 |
64 | const result = await response.json();
65 |
66 | if(result.success) {
67 | // redirect to dashboard
68 | setTimeout(() => {
69 | window.location.href = "dashboard.php";
70 | }, 3000);
71 | }
72 | else {
73 | alert(result.message);
74 | }
75 | }
76 | catch(exception) {
77 | console.log(exception);
78 | }
79 |
80 | })
--------------------------------------------------------------------------------
/public/verifyotp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OTP Verification | SwiftHealth
7 |
8 |
9 |
10 |
11 |
12 |
OTP Verification
13 |
A verification code has been sent to your email. Please enter the 6-digit OTP below.
14 |
15 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/js/login.js:
--------------------------------------------------------------------------------
1 | let loginbutton = document.getElementById("loginbutton");
2 |
3 | let emailField = document.getElementById("emailinput");
4 | let passwordField = document.getElementById("passwordinput");
5 |
6 | let loader = document.getElementById("loader-element");
7 |
8 | let emailError = document.getElementById("emailError");
9 | let passwordError = document.getElementById("passwordError")
10 |
11 | // a basic email vaidation function
12 | function isValidEmail(email) {
13 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14 | return emailPattern.test(email);
15 | }
16 |
17 | loginbutton.addEventListener("click", async () => {
18 |
19 | if(emailField.value === "" || !isValidEmail(emailField.value)) {
20 | emailError.classList.add("max-h-20");
21 | emailError.classList.add("mt-1");
22 | return;
23 | }
24 |
25 | if(passwordField.value === "") {
26 | passwordError.classList.add('max-h-20');
27 | passwordError.classList.add('mt-1');
28 | return;
29 | }
30 | // validated
31 | loader.classList.remove("hidden");
32 | const formdata = new FormData();
33 |
34 | formdata.append("email", emailField.value);
35 | formdata.append("password", passwordField.value);
36 |
37 | let response;
38 | try {
39 | response = await fetch("http://localhost/ca2-project/backend/user_login.php", {
40 | method: "POST",
41 | credentials: "include",
42 | body: formdata
43 | })
44 |
45 | let result = await response.json();
46 |
47 | if(result.success) {
48 | window.location.href = "dashboard.php";
49 | // loader.classList.add('hidden');
50 | }
51 | else {
52 | loader.classList.add('hidden');
53 | alert(result.message);
54 | }
55 | }
56 | catch(exception) {
57 | loader.classList.add('hidden');
58 | console.log(exception);
59 | }
60 |
61 | })
62 |
63 | emailField.addEventListener('focus', () => {
64 | emailError.classList.remove("max-h-20");
65 | emailError.classList.remove("mt-1");
66 | })
67 |
68 | passwordField.addEventListener('focus', () => {
69 | passwordError.classList.remove('max-h-20');
70 | passwordError.classList.remove('mt-1');
71 | })
72 |
73 | document.getElementById("togglePassword").addEventListener("click", function () {
74 | const passwordInput = document.getElementById("passwordinput");
75 | const eyeIcon = document.getElementById("eyeIcon");
76 |
77 | if (passwordInput.type === "password") {
78 | passwordInput.type = "text";
79 | eyeIcon.classList.remove("fa-eye");
80 | eyeIcon.classList.add("fa-eye-slash");
81 | } else {
82 | passwordInput.type = "password";
83 | eyeIcon.classList.remove("fa-eye-slash");
84 | eyeIcon.classList.add("fa-eye");
85 | }
86 | });
87 |
--------------------------------------------------------------------------------
/public/dashboard.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Dashboard | SwiftHealth
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | prepare("SELECT * FROM users WHERE email = ?");
27 | if (!$stmt) {
28 | echo json_encode([
29 | "success" => false,
30 | "message" => "DB Error: " . $conn->error
31 | ]);
32 | exit();
33 | }
34 | $stmt->bind_param("s", $userEmail);
35 | if (!$stmt->execute()) {
36 | echo json_encode([
37 | "success" => false,
38 | "message" => "Error fetching details: " . $stmt->error
39 | ]);
40 | exit();
41 | }
42 | $result = $stmt->get_result()->fetch_assoc();
43 | $stmt->close();
44 |
45 | if (!isset($result['name']) || !isset($result['role'])) {
46 | echo "Unable to fetch user details.
";
47 | die();
48 | }
49 | $user_id = $result['id'];
50 | $user_name = $result['name'];
51 | $user_role = $result['role'];
52 |
53 | // saving the id in session for future use.
54 | $_SESSION['user']['id'] = $user_id;
55 | $_SESSION['user']['role'] = $user_role;
56 | $_SESSION['user']['name'] = $user_name;
57 | ?>
58 |
59 |
60 |
61 |
62 |
63 | SwiftHealth
64 |
65 |
72 |
73 |
= $user_name ?>
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/backend/verify_otp.php:
--------------------------------------------------------------------------------
1 | false,
24 | "message" => "OTP was not sent successfully. Please try again later."
25 | ]);
26 | die();
27 | }
28 |
29 | if ($_SESSION['creds']['code'] != $received_otp) {
30 | echo json_encode([
31 | "success" => false,
32 | "message" => "Incorrect OTP."
33 | ]);
34 | die();
35 | }
36 |
37 |
38 | $config = require __DIR__ . '/../config/db_config.php';
39 | $dsn = sprintf(
40 | 'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
41 | $config['DB_HOST'],
42 | $config['DB_PORT'],
43 | $config['DB_NAME']
44 | );
45 | $pdo = new PDO($dsn, $config['DB_USERNAME'], $config['DB_PASSWORD'], [
46 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
47 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
48 | ]);
49 |
50 | $name = $_SESSION['creds']['name'];
51 | $email = $_SESSION['creds']['email'];
52 | $password = $_SESSION['creds']['password'];
53 |
54 | // -------------------------------------------- TRANSACTION BEGINS ---------------------------------------------------
55 | $pdo->beginTransaction();
56 |
57 | $insertQuery = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)";
58 | $stmt = $pdo->prepare($insertQuery);
59 | if (!$stmt) {
60 | echo json_encode([
61 | "success" => false,
62 | "message" => "Error preparing user statement."
63 | ]);
64 | $pdo->rollBack();
65 | die();
66 | }
67 | if (!$stmt->execute([$name, $email, $password])) {
68 | echo json_encode([
69 | "success" => false,
70 | "message" => "Error creating user."
71 | ]);
72 | $pdo->rollBack();
73 | die();
74 | }
75 | $user_id = $pdo->lastInsertId();
76 |
77 | $stmt2 = $pdo->prepare("INSERT INTO patients (id) VALUES (?)");
78 | if (!$stmt2) {
79 | echo json_encode([
80 | "success" => false,
81 | "message" => "Error preparing patient statement."
82 | ]);
83 | $pdo->rollBack();
84 | die();
85 | }
86 | if (!$stmt2->execute([$user_id])) {
87 | echo json_encode([
88 | "success" => false,
89 | "message" => "Error creating patient."
90 | ]);
91 | $pdo->rollBack();
92 | die();
93 | }
94 |
95 | unset($_SESSION['creds']);
96 | $_SESSION['user'] = ["email" => $email];
97 | http_response_code(201);
98 | echo json_encode([
99 | "success" => true,
100 | "message" => "Successfully registered the user."
101 | ]);
102 | $pdo->commit();
103 | // -------------------------------------- TRANSACTION ENDS -----------------------------------------------------------
104 |
105 | } catch (Exception $e) {
106 | http_response_code(500);
107 | echo json_encode([
108 | "success" => false,
109 | "message" => $e->getMessage(),
110 | // "file" => $e->getFile(),
111 | // "line" => $e->getLine(),
112 | // "trace" => $e->getTraceAsString()
113 | // debug
114 | ]);
115 | }
116 |
--------------------------------------------------------------------------------
/public/js/signup.js:
--------------------------------------------------------------------------------
1 | let signupbtn = document.getElementById("signupbtn");
2 |
3 | const nameField = document.getElementById("name-input");
4 | const emailField = document.getElementById("email-input");
5 | const passwordField = document.getElementById("signupPassword");
6 |
7 | const invalidName = document.getElementById("invalidname");
8 | const invalidEmail = document.getElementById("invalidemail");
9 | const invalidPassword = document.getElementById("invalidpass");
10 | const registeredEmail = document.getElementById("registered-email");
11 |
12 |
13 | let loader = document.getElementById("loader-element");
14 |
15 | function isValidEmail(email) {
16 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
17 | return emailPattern.test(email);
18 | }
19 |
20 | // SIGNUP BUTTON CLICK LISTENER
21 | signupbtn.addEventListener("click", async () => {
22 | if (!nameField.value) {
23 | invalidName.classList.remove("hidden");
24 | return;
25 | }
26 | if (!emailField.value || !isValidEmail(emailField.value)) {
27 | invalidEmail.classList.remove("hidden");
28 | return;
29 | }
30 |
31 | if (!passwordField.value || passwordField.value.length < 8) {
32 | invalidPassword.classList.remove("hidden");
33 | return;
34 | }
35 |
36 | loader.classList.remove("hidden");
37 |
38 | // check if the email already exists
39 |
40 | let emailres = false;
41 |
42 | try {
43 | const formdata = new FormData();
44 | formdata.append("email", emailField.value);
45 | emailres = await fetch(
46 | "http://localhost/ca2-project/backend/verify_email.php",
47 | {
48 | method: "POST",
49 | credentials: "include",
50 | body: formdata,
51 | }
52 | );
53 |
54 | const result = await emailres.json();
55 |
56 | if (!result.success) {
57 | registeredEmail.classList.remove('hidden');
58 | loader.classList.add("hidden");
59 | return;
60 | }
61 | } catch (error) {
62 | loader.classList.add("hidden");
63 | console.log(error);
64 | }
65 |
66 | let response;
67 | try {
68 | const formdata = new FormData();
69 | formdata.append("name", nameField.value);
70 | formdata.append("email", emailField.value);
71 | formdata.append("password", passwordField.value);
72 |
73 | response = await fetch(
74 | "http://localhost/ca2-project/backend/send_otp.php",
75 | {
76 | method: "POST",
77 | credentials: "include",
78 | body: formdata,
79 | }
80 | );
81 | const result = await response.json();
82 |
83 | if (result.success) {
84 | console.log("sent otp");
85 | window.location.href = result.redirect;
86 | } else {
87 | console.error("some error occured");
88 | }
89 | } catch (error) {
90 | console.log(error);
91 | } finally {
92 | loader.classList.add("hidden");
93 | }
94 | });
95 |
96 |
97 | nameField.addEventListener("focus", () => {
98 | invalidName.classList.add("hidden");
99 | });
100 | emailField.addEventListener("focus", () => {
101 | invalidEmail.classList.add('hidden');
102 | registeredEmail.classList.add('hidden');
103 | });
104 | passwordField.addEventListener("focus", () => {
105 | invalidPassword.classList.add('hidden');
106 | });
107 |
108 |
109 | const toggleBtns = document.querySelectorAll(".password-toggle");
110 | toggleBtns.forEach((btn) => {
111 | btn.addEventListener("click", function () {
112 | const input = btn.previousElementSibling;
113 | const icon = btn.querySelector("i");
114 |
115 | if (input.type === "password") {
116 | input.type = "text";
117 | icon.classList.remove("fa-eye");
118 | icon.classList.add("fa-eye-slash");
119 | } else {
120 | input.type = "password";
121 | icon.classList.remove("fa-eye-slash");
122 | icon.classList.add("fa-eye");
123 | }
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/backend/profileapi.php:
--------------------------------------------------------------------------------
1 | false, "message" => "All fields are required"]);
23 | exit();
24 | }
25 |
26 | // Validation
27 | if (!preg_match("/^\d{4}-\d{2}-\d{2}$/", $dob)) {
28 | echo json_encode(["success" => false, "message" => "Invalid date of birth format. Please use YYYY-MM-DD."]);
29 | exit();
30 | }
31 |
32 | if (!preg_match("/^\d{10}$/", $phone)) {
33 | echo json_encode(["success" => false, "message" => "Invalid phone number. Please use a 10-digit number."]);
34 | exit();
35 | }
36 |
37 | if (!is_numeric($height)) {
38 | echo json_encode(["success" => false, "message" => "Height must be numeric value."]);
39 | exit();
40 | }
41 |
42 | if (!is_numeric($weight)) {
43 | echo json_encode(["success" => false, "message" => "Weight must be numeric value."]);
44 | exit();
45 | }
46 |
47 |
48 | $checkQuery = "SELECT id FROM patients WHERE id = ?";
49 | $checkStmt = $conn->prepare($checkQuery);
50 | if (!$checkStmt) {
51 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]);
52 | exit();
53 | }
54 |
55 | $checkStmt->bind_param("i", $id);
56 | $checkStmt->execute();
57 | $result = $checkStmt->get_result();
58 | $patientExists = $result->num_rows > 0;
59 | $checkStmt->close();
60 |
61 |
62 | if (!$patientExists) {
63 | echo json_encode(["success" => false, "message" => "Patient record not found. Please contact support."]);
64 | exit();
65 | }
66 |
67 | // phone and address in users table
68 | $usersQuery = "UPDATE users SET phone = ?, address = ? WHERE id = ?";
69 | $usersStmt = $conn->prepare($usersQuery);
70 |
71 | if (!$usersStmt) {
72 | echo json_encode(["success" => false, "message" => "Error updating user profile: " . $conn->error]);
73 | exit();
74 | }
75 |
76 | $usersStmt->bind_param("ssi", $phone, $address, $id);
77 | $usersSuccess = $usersStmt->execute();
78 | $usersStmt->close();
79 |
80 | if (!$usersSuccess) {
81 | echo json_encode(["success" => false, "message" => "Error updating contact information"]);
82 | exit();
83 | }
84 |
85 | $patientsQuery = "UPDATE patients SET
86 | date_of_birth = ?,
87 | gender = ?,
88 | medical_history = ?,
89 | blood_type = ?,
90 | height = ?,
91 | weight = ?,
92 | emergency_contact = ?
93 | WHERE id = ?";
94 |
95 | $patientsStmt = $conn->prepare($patientsQuery);
96 | if (!$patientsStmt) {
97 | echo json_encode(["success" => false, "message" => "Error updating medical information: " . $conn->error]);
98 | exit();
99 | }
100 |
101 | $patientsStmt->bind_param("sssssssi", $dob, $gender, $medical_history, $blood_type, $height, $weight, $emergency_contact, $id);
102 | $patientsSuccess = $patientsStmt->execute();
103 | $patientsStmt->close();
104 |
105 | if ($patientsSuccess) {
106 | echo json_encode(["success" => true, "message" => "Profile Updated Successfully"]);
107 | } else {
108 | echo json_encode(["success" => false, "message" => "Error updating medical information: " . $conn->error]);
109 | }
110 |
111 | ?>
112 |
--------------------------------------------------------------------------------
/public/js/bookapp.js:
--------------------------------------------------------------------------------
1 | const modalBackdrop = document.getElementById("modal-backdrop");
2 | const modalContainer = document.getElementById("modal-container");
3 | const modalTitle = document.getElementById("modal-title");
4 | const modalMessage = document.getElementById("modal-message");
5 | const successIcon = document.getElementById("success-icon");
6 | const errorIcon = document.getElementById("error-icon");
7 | const closeModal = document.getElementById("close-modal");
8 | const modalCloseBtn = document.getElementById("modal-close-btn");
9 | const loader = document.getElementById("loader-element");
10 |
11 | //show modal
12 | function showModal(success, message) {
13 | loader.classList.add("hidden");
14 | modalMessage.textContent = message;
15 |
16 | if (success) {
17 | modalTitle.textContent = "Appointment Booked";
18 | successIcon.classList.remove("hidden");
19 | errorIcon.classList.add("hidden");
20 | modalTitle.classList.remove("text-red-600");
21 | modalTitle.classList.add("text-green-600");
22 | } else {
23 | modalTitle.textContent = "Booking Failed";
24 | errorIcon.classList.remove("hidden");
25 | successIcon.classList.add("hidden");
26 | modalTitle.classList.remove("text-green-600");
27 | modalTitle.classList.add("text-red-600");
28 | }
29 |
30 | modalBackdrop.classList.remove("hidden");
31 | setTimeout(() => {
32 | modalBackdrop.classList.remove("opacity-0");
33 | modalContainer.classList.remove("scale-95");
34 | modalContainer.classList.add("scale-100");
35 | }, 10);
36 | }
37 |
38 | //hide modal
39 | function hideModal() {
40 | modalBackdrop.classList.add("opacity-0");
41 | modalContainer.classList.remove("scale-100");
42 | modalContainer.classList.add("scale-95");
43 | setTimeout(() => {
44 | modalBackdrop.classList.add("hidden");
45 | }, 300);
46 | }
47 |
48 | // Event listeners to close modal
49 | closeModal.addEventListener("click", hideModal);
50 | modalCloseBtn.addEventListener("click", hideModal);
51 | modalBackdrop.addEventListener("click", (e) => {
52 | if (e.target === modalBackdrop) {
53 | hideModal();
54 | }
55 | });
56 |
57 | let dateInput = document.getElementById("appdate");
58 | let specializationInput = document.getElementById("specializationInput");
59 | let remarksInput = document.getElementById("remarksInput");
60 |
61 | let bookButton = document.getElementById("book-button");
62 |
63 | bookButton.addEventListener("click", async () => {
64 | let dateValue = dateInput.value;
65 | let specializationValue = specializationInput.value;
66 | let remarksValue = remarksInput.value;
67 |
68 | if (!dateValue) {
69 | dateInput.classList.add("ring-2", "ring-red-500");
70 | return;
71 | }
72 |
73 | if (specializationValue === "Select a specialization") {
74 | specializationInput.classList.add("ring-2", "ring-red-500");
75 | return;
76 | }
77 | loader.classList.remove("hidden");
78 |
79 | const formdata = new FormData();
80 | formdata.append("date", dateValue);
81 | formdata.append("specialization", specializationValue);
82 | formdata.append("remarks", remarksValue);
83 |
84 | try {
85 | const res = await fetch(
86 | "http://localhost/ca2-project/backend/book_appointment.php",
87 | {
88 | method: "POST",
89 | credentials: "include",
90 | body: formdata,
91 | }
92 | );
93 |
94 | const result = await res.json();
95 | showModal(result.success, result.message);
96 |
97 | } catch (error) {
98 | showModal(false, "An unexpected error occurred. Please try again.");
99 | console.error("Error:", error);
100 | }
101 | });
102 |
103 | dateInput.addEventListener("focus", () => {
104 | dateInput.classList.remove("ring-2");
105 | });
106 |
107 | specializationInput.addEventListener("focus", () => {
108 | specializationInput.classList.remove("ring-2");
109 | });
110 |
--------------------------------------------------------------------------------
/public/assets/height.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/public/js/editProfile.js:
--------------------------------------------------------------------------------
1 | const profileForm = document.getElementById("profileForm");
2 | const statusMessage = document.getElementById("statusMessage");
3 |
4 |
5 |
6 | function isValidEmail(email) {
7 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8 | return emailPattern.test(email);
9 | }
10 |
11 | profileForm.addEventListener("submit", function (e) {
12 | e.preventDefault();
13 |
14 | // phone number validation
15 | const phoneInput = document.getElementById("phone");
16 | // regex just looks ugly oof
17 | if (!/^\d{10}$/.test(phoneInput.value)) {
18 | statusMessage.innerHTML =
19 | ' Phone number must be 10 digits';
20 | statusMessage.classList.remove(
21 | "hidden",
22 | "bg-green-100",
23 | "text-green-700",
24 | "border-green-500"
25 | );
26 | statusMessage.classList.add("bg-red-100", "text-red-700", "border-red-500");
27 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" });
28 | return;
29 | }
30 |
31 | // Emergency contact email validation
32 | const emergencyContactInput = document.getElementById("emergency_contact");
33 | if (!isValidEmail(emergencyContactInput.value)) {
34 | statusMessage.innerHTML =
35 | ' Emergency contact must be a valid email address';
36 | statusMessage.classList.remove(
37 | "hidden",
38 | "bg-green-100",
39 | "text-green-700",
40 | "border-green-500"
41 | );
42 | statusMessage.classList.add("bg-red-100", "text-red-700", "border-red-500");
43 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" });
44 | return;
45 | }
46 |
47 | // loading state
48 | const submitButton = profileForm.querySelector('button[type="submit"]');
49 | const originalButtonText = submitButton.innerHTML;
50 | submitButton.innerHTML =
51 | ' Saving...';
52 | submitButton.disabled = true;
53 |
54 | // form data
55 | const formData = new FormData(profileForm);
56 |
57 | // submit using fetch
58 | fetch("../backend/profileapi.php", {
59 | method: "POST",
60 | body: formData,
61 | })
62 | .then((response) => response.json())
63 | .then((data) => {
64 | statusMessage.innerHTML = data.success
65 | ? ' ' + data.message
66 | : ' ' + data.message;
67 |
68 | statusMessage.classList.remove(
69 | "hidden",
70 | "bg-red-100",
71 | "text-red-700",
72 | "bg-green-100",
73 | "text-green-700",
74 | "border-red-500",
75 | "border-green-500"
76 | );
77 |
78 | if (data.success) {
79 | statusMessage.classList.add(
80 | "bg-green-100",
81 | "text-green-700",
82 | "border-green-500"
83 | );
84 | } else {
85 | statusMessage.classList.add(
86 | "bg-red-100",
87 | "text-red-700",
88 | "border-red-500"
89 | );
90 | }
91 |
92 | submitButton.innerHTML = originalButtonText;
93 | submitButton.disabled = false;
94 |
95 |
96 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" });
97 |
98 | if (data.success) {
99 | setTimeout(() => {
100 | statusMessage.classList.add("hidden");
101 | }, 5000);
102 | }
103 | })
104 | .catch((error) => {
105 | console.error("Error:", error);
106 | statusMessage.innerHTML =
107 | ' An error occurred. Please try again.';
108 | statusMessage.classList.remove(
109 | "hidden",
110 | "bg-green-100",
111 | "text-green-700",
112 | "border-green-500"
113 | );
114 | statusMessage.classList.add(
115 | "bg-red-100",
116 | "text-red-700",
117 | "border-red-500"
118 | );
119 |
120 |
121 | submitButton.innerHTML = originalButtonText;
122 | submitButton.disabled = false;
123 | });
124 | });
125 |
126 | // Logout button
127 | document.getElementById("logout-btn").addEventListener("click", function () {
128 | window.location.href = "logout.php";
129 | });
130 |
--------------------------------------------------------------------------------
/public/assets/weight.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/backend/book_appointment.php:
--------------------------------------------------------------------------------
1 | false, "message" => "Missing required fields"]);
14 | exit();
15 | }
16 |
17 | $date = $_POST['date'];
18 | $specialization = $_POST['specialization'];
19 | $remarks = $_POST['remarks'];
20 |
21 | $day = strtolower(date('l', strtotime($date))); // e.g., "Monday"
22 | $formatted_date = date("Y-m-d", strtotime($date)); // e.g., "2025-03-19"
23 |
24 | // -------------------- TRANSACTION BEGINS -------------------------
25 | $config = require __DIR__ . '/../config/db_config.php';
26 | $dsn = sprintf(
27 | 'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
28 | $config['DB_HOST'],
29 | $config['DB_PORT'],
30 | $config['DB_NAME']
31 | );
32 | $pdo = new PDO($dsn, $config['DB_USERNAME'], $config['DB_PASSWORD'], [
33 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
34 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
35 | ]);
36 | $pdo->exec("SET session wait_timeout=600");
37 | $pdo->exec("SET session interactive_timeout=600");
38 | $pdo->exec("SET SESSION net_read_timeout=600");
39 | $pdo->beginTransaction();
40 |
41 | try {
42 | // this is a basic doctor finding algorithm on the basis of availabiltiy, it won't be inefficient if the slots per day are less.
43 | // i'll maybe optimize it later by normalizing the scheduling process and mainting a seperate table for booking times.
44 |
45 | // Query1: get all doctors with the given specialization and with available slots on $day
46 | $query1 = "SELECT id, specialization, available_slots FROM doctors
47 | WHERE specialization = ?
48 | AND JSON_CONTAINS_PATH(available_slots, 'one', CONCAT('$.', ?))
49 | ORDER BY id";
50 | $stmt1 = $pdo->prepare($query1);
51 | $stmt1->execute([$specialization, $day]);
52 | $results = $stmt1->fetchAll();
53 | $stmt1 = null;
54 | if (count($results) === 0) {
55 | $pdo->rollBack();
56 | http_response_code(404);
57 | echo json_encode(["success" => false, "message" => "No doctors available on that date"]);
58 | exit();
59 | }
60 | $doctor_found = false;
61 | $doctor_id = null;
62 | $free_time = null;
63 | foreach ($results as $doctor) {
64 | $available_slots_json = $doctor['available_slots'];
65 | $available_slots = json_decode($available_slots_json, true);
66 | if (!isset($available_slots[$day]) || empty($available_slots[$day])) {
67 | continue;
68 | }
69 | $potential_slots = $available_slots[$day];
70 | $query_appts = "SELECT appointment_time FROM appointments
71 | WHERE doctor_id = ? AND DATE(appointment_time) = ?";
72 | $stmt_appts = $pdo->prepare($query_appts);
73 | $stmt_appts->execute([$doctor['id'], $formatted_date]);
74 | $booked_result = $stmt_appts->fetchAll();
75 | $stmt_appts = null;
76 | $booked_slots = [];
77 | foreach ($booked_result as $row) {
78 | $booked_slots[] = date("H:i", strtotime($row['appointment_time']));
79 | }
80 | $free_slots = array_diff($potential_slots, $booked_slots);
81 | if (!empty($free_slots)) {
82 | $doctor_found = true;
83 | $doctor_id = $doctor['id'];
84 | $free_slots = array_values($free_slots);
85 | $free_time = $free_slots[0];
86 | break;
87 | }
88 | }
89 | if (!$doctor_found) {
90 | $pdo->rollBack();
91 | http_response_code(404);
92 | echo json_encode(["success" => false, "message" => "No free slots available for any doctor with that specialization on that date"]);
93 | exit();
94 | }
95 | } catch (Exception $e) {
96 | $pdo->rollBack();
97 | http_response_code(500);
98 | echo json_encode(["success" => false, "message" => "Internal Server Error. Failed to find available doctor: " . $e->getMessage()]);
99 | exit();
100 | }
101 |
102 | try {
103 | $appointment_time = $formatted_date . ' ' . $free_time;
104 | $query3 = "INSERT INTO appointments (patient_id, doctor_id, appointment_time, remarks) VALUES (?, ?, ?, ?)";
105 | $stmt3 = $pdo->prepare($query3);
106 | if (!$stmt3->execute([$userid, $doctor_id, $appointment_time, $remarks])) {
107 | $pdo->rollBack();
108 | http_response_code(500);
109 | echo json_encode(["success" => false, "message" => "Failed to create appointment."]);
110 | exit();
111 | }
112 | $stmt3 = null;
113 | $pdo->commit();
114 | echo json_encode(["success" => true, "message" => "Appointment booked successfully.", "appointment_time" => $appointment_time]);
115 | } catch (Exception $e) {
116 | $pdo->rollBack();
117 | http_response_code(500);
118 | echo json_encode(["success" => false, "message" => "Internal Server Error. Failed to create an appointment: " . $e->getMessage()]);
119 | exit();
120 | }
121 | // --------------------- TRANSACTION ENDS --------------------------
122 | ?>
123 |
--------------------------------------------------------------------------------
/backend/send_otp.php:
--------------------------------------------------------------------------------
1 | load();
33 |
34 | $sender = $_ENV["SENDER_MAIL"];
35 | $sender_pass = $_ENV["SENDER_PASSWORD"];
36 |
37 |
38 | if (!$sender || !$sender_pass) {
39 | die("Environment variables for email not set.");
40 | }
41 |
42 | $mail = new PHPMailer(true);
43 |
44 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL);
45 | $name = filter_var($_POST["name"], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
46 | $password = filter_var($_POST["password"], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
47 |
48 | // hash the password
49 | $hashed_password = password_hash($password, PASSWORD_BCRYPT);
50 |
51 | $otp = random_int(100000, 999999);
52 | $_SESSION['creds'] = [
53 | "name" => $name,
54 | "email" => $email,
55 | "code" => $otp,
56 | "password" => $hashed_password
57 | ];
58 |
59 | $emailBody = "
60 |
61 |
109 |
110 |
111 |
112 |
Your OTP Code for SwiftHealth
113 |
Dear User,
114 |
Thank you for signing up with SwiftHealth. To complete your registration and activate your account, please use the One-Time Password (OTP) below:
115 |
$otp
116 |
This OTP is confidential. Please do not share it with anyone to protect your account.
117 |
If you did not request this, please ignore this message.
118 |
122 |
123 |
124 | ";
125 |
126 | try {
127 | $mail->isSMTP();
128 | $mail->Host = 'smtp.gmail.com';
129 | $mail->SMTPAuth = true;
130 | $mail->Username = $sender;
131 | $mail->Password = $sender_pass;
132 | $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
133 | $mail->Port = 587;
134 |
135 | $mail->setFrom($sender, 'SwiftHealth');
136 | $mail->addAddress($email);
137 | $mail->isHTML(true);
138 | $mail->Subject = 'Your OTP is here.';
139 | $mail->Body = $emailBody;
140 |
141 | $mail->send();
142 | echo json_encode(["success" => true, "message" => "OTP Sent Successfully", "redirect" => "verifyotp.html"]);
143 | } catch (Exception $e) {
144 | echo json_encode(["success" => false, "message" => $e->getMessage(), "redirect" => false]);
145 | unset($_SESSION['otp']);
146 | }
147 |
--------------------------------------------------------------------------------
/public/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Login | SwiftHealth
7 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
SwiftHealth
30 |
31 |
32 |
41 |
42 |
43 |
46 |
47 |
Please wait... Logging you in
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | SwiftHealth
58 |
59 |
Your Complete Healthcare Solution
60 |
61 |
62 |
63 |
Welcome Back!
64 |
Access your health dashboard securely and efficiently.
65 |
66 |
67 |
68 |
69 |
70 | Secure access to your health records
71 |
72 |
73 |
74 | View upcoming appointments
75 |
76 |
77 |
78 | Get personalized health notifications
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
Welcome back! Please login to your account.
88 |
89 |
120 |
121 |
122 |
Don't have an account?
123 | Sign Up
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/backend/emergencymail.php:
--------------------------------------------------------------------------------
1 | load();
28 |
29 | $sender = $_ENV["ALERT_SENDER_MAIL"];
30 | $sender_pass = $_ENV["ALERT_SENDER_PASSWORD"];
31 |
32 | require "./database/connectDB.php";
33 |
34 | if (!$sender || !$sender_pass) {
35 | http_response_code(500);
36 | die(json_encode(["success" => false, "message" => "Server configuration error (missing email credentials)."]));
37 | }
38 |
39 | // Check if user is logged in
40 | if (!isset($_SESSION['user']['id']) || !isset($_SESSION['user']['name'])) {
41 | http_response_code(401);
42 | die(json_encode(["success" => false, "message" => "User not logged in or session expired."]));
43 | }
44 |
45 | $user_id = $_SESSION['user']['id'];
46 | $patient_name = $_SESSION['user']['name'];
47 |
48 |
49 | $input = json_decode(file_get_contents('php://input'), true);
50 | $latitude = isset($input['latitude']) ? filter_var($input['latitude'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : null;
51 | $longitude = isset($input['longitude']) ? filter_var($input['longitude'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : null;
52 | $location_info = "";
53 |
54 | if ($latitude !== null && $longitude !== null) {
55 | $googleMapsLink = "https://www.google.com/maps?q=" . urlencode($latitude . "," . $longitude);
56 | $location_info = "Patient's Last Known Location:
"
57 | . "Latitude: $latitude
Longitude: $longitude
"
58 | . "View on Google Maps
";
59 | } else {
60 | $location_info = "Patient's Location: Not available.
";
61 | }
62 |
63 |
64 |
65 |
66 | $stmt = $conn->prepare("SELECT emergency_contact FROM patients WHERE id = ?");
67 | if (!$stmt) {
68 | http_response_code(500);
69 | die(json_encode(["success" => false, "message" => "Database prepare statement failed: " . $conn->error]));
70 | }
71 | $stmt->bind_param("i", $user_id);
72 | $stmt->execute();
73 | $result = $stmt->get_result();
74 |
75 | if ($result->num_rows === 0) {
76 | $stmt->close();
77 | $conn->close();
78 | http_response_code(404);
79 | die(json_encode(["success" => false, "message" => "Patient record not found."]));
80 | }
81 |
82 | $patient = $result->fetch_assoc();
83 | $emergency_contact = $patient['emergency_contact'];
84 | $stmt->close();
85 |
86 |
87 |
88 | if (!$emergency_contact || !filter_var($emergency_contact, FILTER_VALIDATE_EMAIL)) {
89 | http_response_code(400);
90 | die(json_encode(["success" => false, "message" => "Invalid or missing emergency contact email in patient profile."]));
91 | }
92 |
93 | $mail = new PHPMailer(true);
94 |
95 |
96 | $emailBody = "
97 |
98 |
99 |
111 |
112 |
113 |
114 |
Emergency Alert
115 |
Urgent: Immediate Attention Required!
116 |
117 |
118 |
Patient Name: $patient_name
119 |
This patient has triggered an emergency alert using the SwiftHealth system. Please respond immediately.
120 |
121 |
122 |
123 | $location_info
124 |
125 |
126 |
Time is critical, and your prompt response could be life-saving. Please take necessary action right away.
127 |
128 |
132 |
133 |
134 |
135 | ";
136 |
137 |
138 | try {
139 | // --- PHPMailer Setup ---
140 | $mail->isSMTP();
141 | $mail->Host = 'smtp.gmail.com';
142 | $mail->SMTPAuth = true;
143 | $mail->Username = $sender;
144 | $mail->Password = $sender_pass;
145 | $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
146 | $mail->Port = 587;
147 |
148 | $mail->setFrom($sender, 'SwiftHealth Emergency Alert');
149 | $mail->addAddress($emergency_contact);
150 | $mail->isHTML(true);
151 | $mail->Subject = 'Emergency Alert - Immediate Attention Required for ' . $patient_name;
152 | $mail->Body = $emailBody;
153 | // this was optional but i added it anyway
154 | $mail->AltBody = "Emergency Alert!\n\nPatient Name: $patient_name\nThis patient has triggered an emergency alert. Please respond immediately.\n\n" . strip_tags(str_replace('
', "\n", $location_info)) . "\n\nTime is critical.";
155 |
156 |
157 | $mail->send();
158 | echo json_encode(["success" => true, "message" => "Emergency email sent successfully to $emergency_contact."]);
159 |
160 | } catch (Exception $e) {
161 | error_log("Mailer Error: " . $mail->ErrorInfo);
162 | http_response_code(500);
163 | echo json_encode(["success" => false, "message" => "Failed to send email. Please try again later or contact support."]);
164 | }
165 | ?>
166 |
--------------------------------------------------------------------------------
/public/js/dashboard.js:
--------------------------------------------------------------------------------
1 | let logoutbtn = document.getElementById("logout-btn");
2 | logoutbtn.addEventListener("click", logoutUser);
3 |
4 | async function logoutUser() {
5 | let response = await fetch(
6 | "http://localhost/ca2-project/backend/user_logout.php",
7 | {
8 | method: "POST",
9 | credentials: "include",
10 | }
11 | );
12 |
13 | let result = await response.json();
14 |
15 | if (result.success) {
16 | window.location.href = "login.html";
17 | } else {
18 | alert("An error occured.");
19 | }
20 | }
21 |
22 | const chatPopup = document.getElementById("chat-popup");
23 | const chatToggleBtn = document.getElementById("chat-toggle-btn");
24 | const chatCloseBtn = document.getElementById("chat-close-btn");
25 | const chatSendBtn = document.getElementById("chat-send-btn");
26 | const chatInputField = document.getElementById("chat-input-field");
27 | const chatMessages = document.getElementById("chat-messages");
28 | const typingIndicator = document.getElementById("typing-indicator");
29 | const username = document.getElementById("user_name")?.innerText || "You";
30 |
31 |
32 | chatToggleBtn.addEventListener("click", () => {
33 | chatPopup.classList.toggle("hidden");
34 | if (!chatPopup.classList.contains("hidden")) {
35 | chatInputField.focus();
36 | }
37 | });
38 |
39 |
40 | chatCloseBtn.addEventListener("click", () => {
41 | chatPopup.classList.add("hidden");
42 | });
43 |
44 | // new messg
45 | function addMessage(sender, message, isAI = false) {
46 | const messageElem = document.createElement("div");
47 |
48 | if (isAI) {
49 | // AI message
50 | messageElem.className = "flex items-start mb-4";
51 | messageElem.innerHTML = `
52 |
53 |
54 |
55 |
58 | `;
59 | } else {
60 | // user message
61 | messageElem.className = "flex items-start justify-end mb-4";
62 | messageElem.innerHTML = `
63 |
66 |
67 | ${username
68 | .charAt(0)
69 | .toUpperCase()}
70 |
71 | `;
72 | }
73 |
74 | chatMessages.appendChild(messageElem);
75 | chatMessages.scrollTop = chatMessages.scrollHeight;
76 | }
77 |
78 | async function sendMessage() {
79 | const userInput = chatInputField.value.trim();
80 | if (userInput === "") return;
81 |
82 | // Display user message
83 | addMessage(username, userInput, false);
84 | chatInputField.value = "";
85 |
86 | // Show typing indicator
87 | typingIndicator.classList.remove("hidden");
88 | chatMessages.scrollTop = chatMessages.scrollHeight;
89 |
90 | const formData = new FormData();
91 | formData.append("userinput", userInput);
92 |
93 | try {
94 | const response = await fetch(
95 | "http://localhost/ca2-project/backend/ai_api.php",
96 | {
97 | method: "POST",
98 | body: formData,
99 | }
100 | );
101 |
102 | // Hide typing indicator
103 | typingIndicator.classList.add("hidden");
104 |
105 | const data = await response.json();
106 | const reply = data.candidates[0].content.parts[0].text;
107 |
108 | // Display AI message
109 | addMessage("AI", reply, true);
110 | } catch (error) {
111 | // Hide typing indicator
112 | typingIndicator.classList.add("hidden");
113 |
114 | console.error("Error:", error);
115 | addMessage(
116 | "AI",
117 | "Sorry, I'm having trouble processing your request. Please try again later.",
118 | true
119 | );
120 | }
121 | }
122 |
123 | // Event listeners for sending messages
124 | chatSendBtn.addEventListener("click", sendMessage);
125 | chatInputField.addEventListener("keypress", function (e) {
126 | if (e.key === "Enter") sendMessage();
127 | });
128 |
129 |
130 | const emergencyBtn = document.getElementById("emergency-btn");
131 | const emergencyModal = document.getElementById("emergency-modal");
132 | const confirmEmergencyBtn = document.getElementById("confirm-emergency");
133 | const cancelEmergencyBtn = document.getElementById("cancel-emergency");
134 | const emergencyStatusModal = document.getElementById("emergency-status-modal");
135 | const statusContent = document.getElementById("status-content");
136 | const closeStatusBtn = document.getElementById("close-status");
137 |
138 | emergencyBtn.addEventListener("click", function () {
139 | emergencyModal.classList.remove("hidden");
140 | });
141 |
142 | cancelEmergencyBtn.addEventListener("click", function () {
143 | emergencyModal.classList.add("hidden");
144 | });
145 |
146 | closeStatusBtn.addEventListener("click", function () {
147 | emergencyStatusModal.classList.add("hidden");
148 | });
149 |
150 | confirmEmergencyBtn.addEventListener("click", function () {
151 | emergencyModal.classList.add("hidden");
152 |
153 | // initial status
154 | statusContent.innerHTML = `
155 |
156 |
157 |
158 | Getting Location & Sending Alert
159 | Please wait...
160 | `;
161 | emergencyStatusModal.classList.remove("hidden");
162 |
163 | // Try to get location,
164 | if ("geolocation" in navigator) {
165 | navigator.geolocation.getCurrentPosition(sendAlert, () => sendAlert(), {
166 | timeout: 5000,
167 | maximumAge: 0,
168 | });
169 | } else {
170 | sendAlert();
171 | }
172 |
173 | async function sendAlert(position = null) {
174 | const requestBody = {};
175 | if (position) {
176 | requestBody.latitude = position.coords.latitude;
177 | requestBody.longitude = position.coords.longitude;
178 | }
179 |
180 | try {
181 | const response = await fetch("../backend/emergencymail.php", {
182 | method: "POST",
183 | headers: { "Content-Type": "application/json" },
184 | credentials: "include",
185 | body: JSON.stringify(requestBody),
186 | });
187 |
188 | const data = await response.json();
189 |
190 | if (data.success) {
191 | statusContent.innerHTML = `
192 |
193 |
194 |
195 | Alert Sent Successfully
196 | ${data.message}
197 | `;
198 | } else {
199 | statusContent.innerHTML = `
200 |
201 |
202 |
203 | Alert Failed
204 | ${
205 | data.message || "Could not send emergency alert."
206 | }
207 | `;
208 | }
209 | } catch (error) {
210 | console.error("Emergency alert error:", error);
211 | statusContent.innerHTML = `
212 |
213 |
214 |
215 | Network Error
216 | Could not connect to server. Please try again later.
217 | `;
218 | }
219 | }
220 | });
221 |
--------------------------------------------------------------------------------
/public/assets/patient.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/ContactUs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Contact Us | SwiftHealth
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Working Hour: 08:00 AM to 09:00 PM | Email: info@domainname.com
18 |
19 |
20 | | Contact: +1 234 567 890
21 |
22 |
23 |
24 |
25 |
49 |
50 |
51 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Any Queries? Contact Us
72 |
Reach out to us for appointments, emergency alerts, and queries.
73 |
74 |
89 |
90 |
91 |
For emergency support, call +x xxx xxx xxxx
92 |
Email us at xxxxx@gmail.com
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/public/signup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Sign Up | SwiftHealth
8 |
9 |
10 |
11 |
12 |
15 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
SwiftHealth
33 |
34 |
35 |
44 |
45 |
46 |
49 |
50 |
Creating your healthcare profile...
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | SwiftHealth
63 |
64 |
Your Complete Healthcare Solution
65 |
66 |
67 |
68 |
69 |
Your Healthcare Assistant Awaits!
70 |
Join thousands of users managing their health effortlessly.
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
Easy appointment scheduling
79 |
80 |
81 |
82 |
83 |
84 |
Connect with top specialists
85 |
86 |
87 |
88 |
89 |
90 |
Secure health record management
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
Start your journey to better health management
106 |
107 |
108 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/backend/views/doctor_dashboard.php:
--------------------------------------------------------------------------------
1 | 'text-green-500 border-green-500 bg-green-50',
11 | 'pending' => 'text-yellow-500 border-yellow-500 bg-yellow-50',
12 | 'rejected' => 'text-red-500 border-red-500 bg-red-50',
13 | 'completed' => 'text-blue-500 border-blue-500 bg-blue-50',
14 | 'cancelled' => 'text-gray-500 border-gray-500 bg-gray-50'
15 | ];
16 |
17 | $statusIcons = [
18 | 'approved' => '',
19 | 'pending' => '',
20 | 'rejected' => '',
21 | 'completed' => '',
22 | 'cancelled' => ''
23 | ];
24 |
25 | $appointmentID = e($appointment['id']);
26 | $status = e($appointment['status']);
27 | $patientName = e($appointment['patient_name']);
28 | $patientEmail = e($appointment['patient_email']);
29 |
30 | $time = date("F j, Y - g:i A", strtotime($appointment['appointment_time']));
31 | $remarks = !empty($appointment['remarks']) ? e($appointment['remarks']) : "No remarks provided.";
32 |
33 | $borderClass = $statusColors[$status] ?? 'border-gray-500 bg-gray-50';
34 | $statusIcon = $statusIcons[$status] ?? '';
35 |
36 | if($status == "approved") {
37 | $completed = "";
38 | }
39 | else {
40 | $completed = "disabled";
41 | }
42 |
43 | if($status != "pending") {
44 | $actionbuttons = "disabled";
45 | }
46 | else {
47 | $actionbuttons = "";
48 | }
49 |
50 | return <<
52 |
53 |
54 |
55 | Patient:
56 | $patientName
57 |
58 |
59 | $statusIcon $status
60 |
61 |
62 |
63 |
64 |
65 |
66 | Email:
67 | $patientEmail
68 |
69 |
70 |
71 |
72 | Time:
73 | $time
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Remarks:
82 | $remarks
83 |
84 |
85 |
86 |
87 |
88 |
91 |
94 |
97 |
98 |
99 | HTML;
100 | }
101 |
102 | $app_query = "SELECT a.id, a.remarks, a.appointment_time, a.status,
103 | p.name AS patient_name, p.email AS patient_email,
104 | u.name AS doctor_name, u.email AS doctor_email
105 | FROM appointments AS a
106 | JOIN users AS u ON u.id = a.doctor_id
107 | JOIN users AS p ON p.id = a.patient_id
108 | WHERE u.email = ? AND a.appointment_time > NOW() AND a.status != 'completed'
109 | ORDER BY a.appointment_time ASC
110 | ";
111 |
112 | $doc_stmt = $conn->prepare($app_query);
113 | if (!$doc_stmt) {
114 | echo ' Error Fetching Details. Try again later.
';
115 | die();
116 | }
117 |
118 | $doc_stmt->bind_param('s', $userEmail);
119 | if (!$doc_stmt->execute()) {
120 | echo ' Error Fetching Details. Try again later.
';
121 | die();
122 | }
123 |
124 | $appointments = $doc_stmt->get_result()->fetch_all(MYSQLI_ASSOC);
125 | $doc_stmt->close();
126 |
127 | $past_query = "SELECT a.id, a.remarks, a.appointment_time, a.status,
128 | p.name AS patient_name, p.email AS patient_email,
129 | u.name AS doctor_name, u.email AS doctor_email
130 | FROM appointments AS a
131 | JOIN users AS u ON u.id = a.doctor_id
132 | JOIN users AS p ON p.id = a.patient_id
133 | WHERE u.email = ? AND (a.appointment_time <= NOW() OR a.status = 'completed')
134 | ORDER BY a.appointment_time DESC
135 | LIMIT 10;";
136 | $doc_stmt2 = $conn->prepare($past_query);
137 | if (!$doc_stmt2) {
138 | echo ' Error Fetching Details. Try again later.
';
139 | die();
140 | }
141 | $doc_stmt2->bind_param('s', $userEmail);
142 |
143 | if (!$doc_stmt2->execute()) {
144 | echo ' Error Fetching Details. Try again later.
';
145 | die();
146 | }
147 |
148 | $past_appointments = $doc_stmt2->get_result()->fetch_all(MYSQLI_ASSOC);
149 | $doc_stmt2->close();
150 |
151 | // Count appointments by status
152 | $pending_count = 0;
153 | $approved_count = 0;
154 | $completed_count = 0;
155 |
156 | foreach($appointments as $appointment) {
157 | if($appointment['status'] == 'pending') $pending_count++;
158 | if($appointment['status'] == 'approved') $approved_count++;
159 | }
160 |
161 | foreach($past_appointments as $appointment) {
162 | if($appointment['status'] == 'completed') $completed_count++;
163 | }
164 |
165 | ?>
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | Doctor Dashboard
176 |
177 |
Welcome back = e($user_name) ?>
178 |
179 |
180 |
181 |
182 |
= date("F j, Y") ?>
183 |
Manage your appointments efficiently
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
Pending
200 |
= $pending_count ?> Appointments
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
Approved
212 |
= $approved_count ?> Appointments
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
Completed
224 |
= $completed_count ?> Appointments
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | Upcoming Appointments
235 |
236 |
237 | ';
246 | }
247 | else {
248 | foreach ($appointments as $appointment) {
249 | echo renderAppointment($appointment);
250 | }
251 | }
252 | ?>
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | Past / Completed Appointments
262 |
263 |
267 |
268 | ';
276 | }
277 | else {
278 | foreach ($past_appointments as $appointment) {
279 | echo renderAppointment($appointment);
280 | }
281 | }
282 | ?>
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
SwiftHealth
292 |
293 |
294 |
303 |
304 |
305 |
308 |
309 |
Please Wait...
310 |
311 |
312 |
313 |
323 |
324 |
325 |
326 |
--------------------------------------------------------------------------------
/public/editProfile.php:
--------------------------------------------------------------------------------
1 | $userId,
15 | 'name' => $userName,
16 | 'email' => $userEmail,
17 | 'date_of_birth' => '',
18 | 'gender' => '',
19 | 'medical_history' => '',
20 | 'blood_type' => '',
21 | 'phone' => '',
22 | 'address' => '',
23 | 'height' => '',
24 | 'weight' => '',
25 | 'emergency_contact' => ''
26 | ];
27 |
28 | // Fetch froim get_user.php
29 | require __DIR__ . "/../backend/get_user.php";
30 | $response = getUserData($userEmail);
31 |
32 | if ($response["success"] && isset($response["data"]) && is_array($response["data"])) {
33 | $userData = array_merge($userData, $response["data"]);
34 | }
35 |
36 | // Get initials for avatar
37 | $initials = strtoupper(substr($userName, 0, 1));
38 | ?>
39 |
40 |
41 |
42 |
43 |
44 |
45 | User Profile - SwiftHealth
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | SwiftHealth
59 |
60 |
61 |
62 | Dashboard
63 |
64 |
65 |
66 | = htmlspecialchars($userName) ?>
67 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | = $initials ?>
81 |
82 |
83 |
= htmlspecialchars($userName) ?>'s Profile
84 |
= htmlspecialchars($userEmail) ?>
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
260 |
261 |
262 |
263 |
265 |
266 |
--------------------------------------------------------------------------------
/backend/views/patient_dashboard.php:
--------------------------------------------------------------------------------
1 | 'text-green-500 border-green-500 bg-green-50',
16 | 'pending' => 'text-yellow-500 border-yellow-500 bg-yellow-50',
17 | 'rejected' => 'text-red-500 border-red-500 bg-red-50',
18 | 'completed' => 'text-blue-500 border-blue-500 bg-blue-50',
19 | 'cancelled' => 'text-gray-500 border-gray-500 bg-gray-50'
20 | ];
21 |
22 | $statusIcons = [
23 | 'approved' => '',
24 | 'pending' => '',
25 | 'rejected' => '',
26 | 'completed' => '',
27 | 'cancelled' => ''
28 | ];
29 |
30 | $status = e($appointment['status']);
31 | $doctorName = e($appointment['doctor_name']);
32 | $specialization = e($appointment['specialization']);
33 | $hospital = e($appointment['hospital_name']);
34 | $time = date("F j, Y - g:i A", strtotime($appointment['appointment_time']));
35 | $remarks = !empty($appointment['remarks']) ? e($appointment['remarks']) : "No remarks provided.";
36 |
37 | $borderClass = $statusColors[$status] ?? 'border-gray-500 bg-gray-50';
38 | $statusIcon = $statusIcons[$status] ?? '';
39 |
40 | return <<
42 |
43 |
44 |
45 | $doctorName
46 |
47 |
48 | $statusIcon $status
49 |
50 |
51 |
52 |
53 |
54 |
55 | Hospital:
56 | $hospital
57 |
58 |
59 |
60 |
61 | Time:
62 | $time
63 |
64 |
65 |
66 |
67 |
68 | Specialization:
69 | $specialization
70 |
71 |
72 |
73 |
74 |
75 |
76 | Remarks:
77 | $remarks
78 |
79 |
80 |
81 |
82 | HTML;
83 | }
84 |
85 | // fetch the list of upcoming appointments.
86 | $app_query = "SELECT
87 | a.id,
88 | a.remarks,
89 | a.appointment_time,
90 | a.status,
91 | d.specialization,
92 | d.hospital_name,
93 | u_patient.name AS patient_name,
94 | u_doctor.name AS doctor_name
95 | FROM appointments AS a
96 | JOIN users AS u_patient ON a.patient_id = u_patient.id
97 | LEFT JOIN doctors AS d ON a.doctor_id = d.id
98 | LEFT JOIN users AS u_doctor ON d.id = u_doctor.id
99 | WHERE u_patient.email = ? AND a.appointment_time > NOW() AND a.status != 'completed'
100 | ORDER BY a.appointment_time ASC
101 | ;";
102 |
103 | $stmt2 = $conn->prepare($app_query);
104 |
105 | if (!$stmt2) {
106 | echo ' Error fetching details, please try again later
';
107 | exit();
108 | }
109 | $stmt2->bind_param("s", $userEmail);
110 | if (!$stmt2->execute()) {
111 | echo ' Error executing query, please try again later
';
112 | exit();
113 | }
114 |
115 | $appointments = $stmt2->get_result()->fetch_all(MYSQLI_ASSOC);
116 | $stmt2->close();
117 |
118 | $past_query = "SELECT
119 | a.id,
120 | a.remarks,
121 | a.appointment_time,
122 | a.status,
123 | d.specialization,
124 | d.hospital_name,
125 | u_doctor.name as doctor_name
126 | FROM appointments as a
127 | JOIN users as u_patient on a.patient_id = u_patient.id
128 | LEFT JOIN doctors as d on a.doctor_id = d.id
129 | LEFT JOIN users as u_doctor on d.id = u_doctor.id
130 | WHERE u_patient.email = ? AND (a.appointment_time <= NOW() OR a.status = 'completed')
131 | ORDER BY a.appointment_time DESC LIMIT 10;
132 | ";
133 |
134 | $past_stmt = $conn->prepare($past_query);
135 |
136 | if(!$past_stmt) {
137 | echo ' Error fetching details, please try again later
';
138 | exit();
139 | }
140 |
141 | $past_stmt->bind_param('s', $userEmail);
142 |
143 | if(!$past_stmt->execute()) {
144 | echo ' Error executing query, please try again later
';
145 | exit();
146 | }
147 |
148 | $past_appointments = $past_stmt->get_result()->fetch_all(MYSQLI_ASSOC);
149 | $past_stmt->close();
150 | ?>
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | Patient Dashboard
161 |
162 |
Welcome back to SwiftHealth
163 |
164 |
165 |
166 |
167 |
= date("F j, Y") ?>
168 |
Manage your health efficiently
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
Upcoming
185 |
= count($appointments) ?> Appointments
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
Past
197 |
= count($past_appointments) ?> Appointments
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
Health Status
209 |
Good
210 |
211 |
212 |
213 |
214 |
215 |
';
234 | }
235 | else {
236 | foreach ($appointments as $appointment) {
237 | echo renderAppointment($appointment);
238 | }
239 | }
240 | ?>
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 | Past / Completed Appointments
250 |
251 |
255 |
256 | ';
264 | }
265 | else {
266 | foreach ($past_appointments as $appointment) {
267 | echo renderAppointment($appointment);
268 | }
269 | }
270 | ?>
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | Emergency Access
280 |
281 |
Need immediate medical attention? Click the emergency button below.
282 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 | Health Tips
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
Regular Check-ups
303 |
Schedule a check-up at least once a year to maintain good health.
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
Balanced Diet
312 |
Maintain a balanced diet rich in fruits, vegetables, and whole grains.
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
Physical Activity
321 |
Aim for at least 30 minutes of moderate exercise daily.
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
367 |
368 |
369 |
370 |
371 |
372 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 | SwiftHealth Assistant
385 |
386 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
Hello! I'm your SwiftHealth Assistant. How can I help you today?
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
426 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
--------------------------------------------------------------------------------
/public/bookappointment.php:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Book Appointment | SwiftHealth
44 |
45 |
46 |
47 |
48 |
49 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
Working Hour: 08:00 AM to 09:00 PM | Email: info@swifthealth.com
66 |
67 |
68 | | Contact: +1 234 567 890
69 |
70 |
71 |
72 |
73 |
97 |
98 |
99 |
111 |
112 |
113 |
114 |
115 |
116 |
Make An Appointment
117 |
118 | HOME / APPOINTMENT
119 |
120 |
121 |
122 |
123 |
124 |
125 |
188 |
189 |
190 |
Make an appointment
191 |
Schedule your healthcare service with ease. Choose a date and specialist that works best for you.
192 |
193 |
194 |
197 |
198 |
Customer Services
199 |
+1 (555) 123-4567
200 |
201 |
202 |
203 |
204 |
207 |
208 |
Opening Hours
209 |
Mon - Sat (09:00 - 21:00)
Sunday (Closed)
210 |
211 |
212 |
213 |
214 |
217 |
218 |
Our Location
219 |
123 Health Avenue, Medical District
New York, NY 10001
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
How We Work
230 |
We work to achieve better health outcomes
231 |
We are committed to improving health outcomes through personalized care, innovative treatments, and a focus on prevention.
232 |
233 |
234 |
235 |

236 |
237 |
1
238 |
Create Account
239 |
Join our community by creating an account today.
240 |
241 |
242 |
243 |
244 |

245 |
246 |
2
247 |
Book Appointment
248 |
Effortlessly book an appointment according to your needs.
249 |
250 |
251 |
252 |
253 |

254 |
255 |
3
256 |
Schedule Appointment
257 |
Our scheduling algorithm will assign a doctor to you.
258 |
259 |
260 |
261 |
262 |

263 |
264 |
4
265 |
Start Consultation
266 |
Consult the doctor after approval.
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
348 |
349 |
350 |
351 |
393 |
394 |
395 |
396 |
397 |
398 |
SwiftHealth
399 |
400 |
401 |
409 |
410 |
411 |
414 |
415 |
Finding the best doctors for you...
416 |
417 |
418 |
419 |
420 |
421 |
422 |
--------------------------------------------------------------------------------
/public/userProfile.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | SwiftHealth - Patient Dashboard
18 |
19 |
20 |
21 |
22 |
23 |
38 |
39 |
168 |
169 |
170 |
171 |
172 |
196 |
197 |
198 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
My Health Dashboard
218 |
Manage your health profile and monitor your medical information
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
235 |
236 |
237 |

238 |
239 |
240 |
241 |
242 |
243 | Bangalore, India
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
Phone Number
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
Email Address
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
Date of Birth
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
Emergency Contact
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
Complete Address
307 |
308 |
309 |
310 |
311 |
312 |
313 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
Health Metrics
329 | Last updated: 02 Apr 2025
330 |
331 |
332 |
333 |
334 |
335 |

336 |
337 |
HEIGHT
338 |
339 |
340 |
341 |
342 |
343 |

344 |
345 |
WEIGHT
346 |
347 |
348 |
349 |
350 |
351 |

352 |
353 |
BLOOD GROUP
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 | Body Mass Index (BMI)
362 | Normal
363 |
364 |
22.4
365 |
366 |
369 |
370 | Underweight
371 | Normal
372 | Overweight
373 | Obese
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
Medical History
384 |
385 |
386 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
430 |
431 |
432 |
444 |
445 |
--------------------------------------------------------------------------------