diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..c8786d4 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,132 @@ +/* General & Typography */ +body { + font-family: 'Inter', sans-serif; + background-color: #F9FAFB; + color: #1F2937; + padding-top: 56px; /* Offset for fixed navbar */ +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Georgia', serif; +} + +/* Navbar */ +.navbar { + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +.navbar-brand { + font-family: 'Georgia', serif; + font-weight: 700; + font-size: 1.5rem; +} + +/* Hero Section */ +.hero { + position: relative; + padding: 8rem 0; + background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://picsum.photos/seed/medhero/1600/900'); + background-size: cover; + background-position: center; + background-attachment: fixed; +} + +.hero h1 { + font-weight: 700; +} + +/* Buttons */ +.btn-primary { + background-color: #3B82F6; + border-color: #3B82F6; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + transition: all 0.3s ease; +} + +.btn-primary:hover { + background-color: #2563EB; + border-color: #2563EB; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.1); +} + +/* Cards */ +.card { + border: none; + border-radius: 1rem; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 20px rgba(0,0,0,0.08); +} + +#checker .card { + border-radius: 1rem; +} + +/* Forms */ +.form-control { + border-radius: 0.5rem; + padding: 0.75rem; +} + +.form-control:focus { + box-shadow: 0 0 0 0.25rem rgba(59, 130, 246, 0.25); + border-color: #3B82F6; +} + +/* Interaction Result */ +#interaction-result .alert { + border-radius: 0.5rem; +} + +.alert-success { + background-color: #E0F2F1; /* Lighter green */ + color: #0D6B61; + border-color: #B2DFDB; +} + +.alert-warning { + background-color: #FFF3E0; /* Lighter orange */ + color: #A65B00; + border-color: #FFE0B2; +} + +.alert-danger { + background-color: #FFEBEE; /* Lighter red */ + color: #B71C1C; + border-color: #FFCDD2; +} + +/* Footer */ +footer a:hover { + text-decoration: underline; +} + +/* Expiry Tracker */ +#medicine-list-container .list-group-item { + display: flex; + justify-content: space-between; + align-items: center; +} + +.expiry-date { + font-weight: 500; +} + +.expiry-soon { + color: #A65B00; /* Orange from alert-warning */ +} + +.expired { + color: #B71C1C; /* Red from alert-danger */ +} + +#medicine-list-container .btn-sm { + --bs-btn-padding-y: .2rem; + --bs-btn-padding-x: .4rem; + --bs-btn-font-size: .75rem; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..54d65c9 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,250 @@ +document.addEventListener('DOMContentLoaded', function () { + + // Smooth scrolling for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + document.querySelector(this.getAttribute('href')).scrollIntoView({ + behavior: 'smooth' + }); + }); + }); + + // Interaction Checker Logic + const interactionForm = document.getElementById('interaction-form'); + const resultDiv = document.getElementById('interaction-result'); + + if (interactionForm) { + interactionForm.addEventListener('submit', function (e) { + e.preventDefault(); + const drugA = document.getElementById('drugA').value.trim(); + const drugB = document.getElementById('drugB').value.trim(); + + resultDiv.className = 'mt-4 d-none'; // Hide previous result + resultDiv.innerHTML = '
Loading...
'; + resultDiv.classList.remove('d-none'); + + setTimeout(() => { + let resultMessage = ''; + let alertClass = 'alert-success'; + + if (drugA.toLowerCase() === 'warfarin' && drugB.toLowerCase() === 'aspirin' || drugA.toLowerCase() === 'aspirin' && drugB.toLowerCase() === 'warfarin') { + resultMessage = `High Risk Interaction: Combining ${drugA} and ${drugB} increases the risk of bleeding. Consult your doctor immediately.`; + alertClass = 'alert-danger'; + } else if (drugA === '' || drugB === '') { + resultMessage = 'Please enter both drug names.'; + alertClass = 'alert-warning'; + } else { + resultMessage = `No Major Interaction Found: No significant interaction was found between ${drugA} and ${drugB}. This is not a substitute for professional medical advice.`; + } + + resultDiv.innerHTML = `
${resultMessage}
`; + }, 1500); + }); + } + + // Contact Form Logic + const contactForm = document.getElementById('contact-form'); + const contactToastEl = document.getElementById('contact-toast'); + const contactToast = new bootstrap.Toast(contactToastEl); + + if (contactForm) { + contactForm.addEventListener('submit', function(e) { + e.preventDefault(); + const name = document.getElementById('name').value; + const email = document.getElementById('email').value; + const message = document.getElementById('message').value; + const submitButton = contactForm.querySelector('button[type="submit"]'); + + const formData = new FormData(); + formData.append('name', name); + formData.append('email', email); + formData.append('message', message); + + submitButton.disabled = true; + submitButton.innerHTML = ' Sending...'; + + fetch('contact.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + const toastBody = contactToastEl.querySelector('.toast-body'); + if (data.success) { + toastBody.textContent = data.message; + contactToastEl.classList.remove('bg-danger'); + contactToastEl.classList.add('bg-success', 'text-white'); + contactForm.reset(); + } else { + toastBody.textContent = data.message || 'An error occurred.'; + contactToastEl.classList.remove('bg-success'); + contactToastEl.classList.add('bg-danger', 'text-white'); + } + contactToast.show(); + }) + .catch(error => { + const toastBody = contactToastEl.querySelector('.toast-body'); + toastBody.textContent = 'A network error occurred. Please try again.'; + contactToastEl.classList.remove('bg-success'); + contactToastEl.classList.add('bg-danger', 'text-white'); + contactToast.show(); + }) + .finally(() => { + submitButton.disabled = false; + submitButton.innerHTML = 'Send Message'; + }); + }); + } + + // Expiry Alert Tracker Logic + const medicineForm = document.getElementById('medicine-form'); + const medicineListContainer = document.getElementById('medicine-list-container'); + + // Function to calculate date difference and apply styling + const getExpiryStatus = (expiryDate) => { + const now = new Date(); + const expiry = new Date(expiryDate); + // Reset time part to compare dates only + now.setHours(0, 0, 0, 0); + expiry.setHours(0, 0, 0, 0); + + const diffTime = expiry - now; + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays < 0) { + return { text: `Expired on ${expiry.toLocaleDateString()}`, className: 'expired' }; + } + if (diffDays <= 7) { + return { text: `Expires in ${diffDays} day(s)`, className: 'expiry-soon' }; + } + return { text: `Expires on ${expiry.toLocaleDateString()}`, className: '' }; + }; + + // Function to render medicines + const renderMedicines = (medicines) => { + if (!medicines || medicines.length === 0) { + medicineListContainer.innerHTML = '

No medicines being tracked yet.

'; + return; + } + + const list = document.createElement('ul'); + list.className = 'list-group list-group-flush'; + + medicines.forEach(medicine => { + const status = getExpiryStatus(medicine.expiry_date); + const item = document.createElement('li'); + item.className = 'list-group-item'; + item.innerHTML = ` +
+ ${medicine.medicine_name} +
+ ${status.text} +
+ + `; + list.appendChild(item); + }); + + medicineListContainer.innerHTML = ''; + medicineListContainer.appendChild(list); + }; + + // Function to fetch medicines from the server + const fetchMedicines = () => { + fetch('medicines.php?action=get') + .then(response => response.json()) + .then(data => { + if (data.success) { + renderMedicines(data.medicines); + } else { + console.error('Failed to fetch medicines:', data.message); + medicineListContainer.innerHTML = '

Could not load medicines.

'; + } + }) + .catch(error => { + console.error('Error fetching medicines:', error); + medicineListContainer.innerHTML = '

Error loading medicines.

'; + }); + }; + + // Event listener for adding a new medicine + if (medicineForm) { + medicineForm.addEventListener('submit', function (e) { + e.preventDefault(); + const medicineName = document.getElementById('medicine_name').value.trim(); + const expiryDate = document.getElementById('expiry_date').value; + const submitButton = medicineForm.querySelector('button[type="submit"]'); + + const formData = new FormData(); + formData.append('action', 'add'); + formData.append('medicine_name', medicineName); + formData.append('expiry_date', expiryDate); + + submitButton.disabled = true; + submitButton.innerHTML = ' Adding...'; + + fetch('medicines.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + medicineForm.reset(); + fetchMedicines(); // Refresh the list + } else { + alert(`Error: ${data.message}`); + } + }) + .catch(error => { + alert('A network error occurred.'); + }) + .finally(() => { + submitButton.disabled = false; + submitButton.innerHTML = 'Add to Tracker'; + }); + }); + } + + // Event listener for deleting a medicine (using event delegation) + if (medicineListContainer) { + medicineListContainer.addEventListener('click', function(e) { + if (e.target && e.target.classList.contains('delete-medicine')) { + const medicineId = e.target.getAttribute('data-id'); + if (!confirm('Are you sure you want to remove this medicine?')) { + return; + } + + const formData = new FormData(); + formData.append('action', 'delete'); + formData.append('id', medicineId); + + e.target.disabled = true; + + fetch('medicines.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + fetchMedicines(); // Refresh the list + } else { + alert(`Error: ${data.message}`); + e.target.disabled = false; + } + }) + .catch(error => { + alert('A network error occurred.'); + e.target.disabled = false; + }); + } + }); + } + + // Initial fetch of medicines when the page loads + if (medicineListContainer) { + fetchMedicines(); + } +}); diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..579300f --- /dev/null +++ b/contact.php @@ -0,0 +1,60 @@ + false, 'message' => 'Invalid request method.']); + exit; +} + +$name = trim($_POST['name'] ?? ''); +$email = trim($_POST['email'] ?? ''); +$message = trim($_POST['message'] ?? ''); + +if (empty($name) || empty($email) || empty($message)) { + echo json_encode(['success' => false, 'message' => 'Please fill out all fields.']); + exit; +} + +if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + echo json_encode(['success' => false, 'message' => 'Please provide a valid email address.']); + exit; +} + +try { + $pdo = db(); + + // Create table if it doesn't exist (idempotent) + $pdo->exec("CREATE TABLE IF NOT EXISTS contact_submissions ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"); + + // Insert submission + $stmt = $pdo->prepare("INSERT INTO contact_submissions (name, email, message) VALUES (?, ?, ?)"); + $stmt->execute([$name, $email, $message]); + + // Send email notification + // The recipient is determined by the MAIL_TO env var by default. + $mailResult = MailService::sendContactMessage($name, $email, $message); + + if ($mailResult['success']) { + echo json_encode(['success' => true, 'message' => 'Thank you! Your message has been sent.']); + } else { + // Still a success for the user, but log the mail error. + error_log("Contact form saved to DB, but mail sending failed: " . ($mailResult['error'] ?? 'Unknown error')); + echo json_encode(['success' => true, 'message' => 'Thank you! Your message has been received.']); + } + +} catch (PDOException $e) { + error_log("Database error: " . $e->getMessage()); + echo json_encode(['success' => false, 'message' => 'A server error occurred while saving your message.']); +} catch (Exception $e) { + error_log("General error: " . $e->getMessage()); + echo json_encode(['success' => false, 'message' => 'A server error occurred.']); +} diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..9cff012 --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,33 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $migrationsDir = __DIR__ . '/migrations'; + if (!is_dir($migrationsDir)) { + mkdir($migrationsDir, 0775, true); + } + + $files = glob($migrationsDir . '/*.sql'); + sort($files); + + foreach ($files as $file) { + $sql = file_get_contents($file); + if (!empty(trim($sql))) { + $pdo->exec($sql); + echo "Executed migration: " . basename($file) . " +"; + } + } + + echo "Migrations completed successfully. +"; + +} catch (PDOException $e) { + http_response_code(500); + die("Migration failed: " . $e->getMessage() . " +"); +} +?> \ No newline at end of file diff --git a/db/migrations/001_create_medicines_table.sql b/db/migrations/001_create_medicines_table.sql new file mode 100644 index 0000000..017343a --- /dev/null +++ b/db/migrations/001_create_medicines_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `medicines` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `medicine_name` VARCHAR(255) NOT NULL, + `expiry_date` DATE NOT NULL, + `added_on` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..e788ab0 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,217 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + MediScan - Smart Medicine Management + + + + + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + + + +
+
+

Effortless Medication Safety.

+

Scan, check, and manage your medications with confidence. Real-time interaction alerts and expiry tracking at your fingertips.

+ Check Interactions Now +
+
+ +
+
+
+
+
+
+
+

Drug Interaction Checker

+
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+

Core Features

+
+
+
+
+
Instant Scanning
+

Use your camera to scan medicine barcodes or packaging to instantly pull up details.

+
+
+
+
+
+
+
Interaction Alerts
+

Cross-reference multiple medications to receive clear warnings about potential drug interactions.

+
+
+
+
+
+
+
Expiry Tracking
+

Never wonder about an expiry date again. Get automatic reminders for your medicines.

+
+
+
+
+
+
+ +
+
+
+
+

About MediScan

+

MediScan was born from a need for a simpler, more accessible way to manage medication safety. Our mission is to empower patients, pharmacists, and doctors with a tool that provides clear, instant, and reliable information. By bridging the gap between medical data and the user, we aim to reduce adverse drug reactions and improve overall health outcomes.

+
+
+ A diverse group of healthcare professionals collaborating. +
+
+
+
+ +
+
+

Expiry Alert Tracker

+
+ +
+
+
+
Add New Medicine
+
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+
+
+
Tracked Medicines
+
+
Loading...
+
+
+
+
+
+
+
+ +
+
+

Get In Touch

+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+ + + +
+ -

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
- + + + - + \ No newline at end of file diff --git a/medicines.php b/medicines.php new file mode 100644 index 0000000..9ca938c --- /dev/null +++ b/medicines.php @@ -0,0 +1,45 @@ + false, 'message' => 'Invalid request.']; +$action = $_REQUEST['action'] ?? null; + +try { + $pdo = db(); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'add') { + $medicineName = trim($_POST['medicine_name'] ?? ''); + $expiryDate = trim($_POST['expiry_date'] ?? ''); + + if (empty($medicineName) || empty($expiryDate)) { + $response['message'] = 'Medicine name and expiry date are required.'; + } else { + $stmt = $pdo->prepare("INSERT INTO medicines (medicine_name, expiry_date) VALUES (:name, :date)"); + $stmt->execute(['name' => $medicineName, 'date' => $expiryDate]); + $response = ['success' => true, 'message' => 'Medicine added successfully.']; + } + } elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'get') { + $stmt = $pdo->query("SELECT id, medicine_name, expiry_date FROM medicines ORDER BY expiry_date ASC"); + $medicines = $stmt->fetchAll(PDO::FETCH_ASSOC); + $response = ['success' => true, 'medicines' => $medicines]; + } elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'delete') { + $id = $_POST['id'] ?? null; + if ($id) { + $stmt = $pdo->prepare("DELETE FROM medicines WHERE id = :id"); + $stmt->execute(['id' => $id]); + $response = ['success' => true, 'message' => 'Medicine removed.']; + } else { + $response['message'] = 'Medicine ID is required.'; + } + } + +} catch (PDOException $e) { + http_response_code(500); + $response['message'] = 'Database error: ' . $e->getMessage(); +} + +echo json_encode($response); +?> \ No newline at end of file diff --git a/privacy.php b/privacy.php new file mode 100644 index 0000000..a54d255 --- /dev/null +++ b/privacy.php @@ -0,0 +1,28 @@ + + + + + + + Privacy Policy - MediScan + + + + +
+

Privacy Policy

+

This is a placeholder for the Privacy Policy.

+

Your privacy is important to us. It is MediScan's policy to respect your privacy regarding any information we may collect from you across our website.

+

We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.

+

We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification.

+

We don’t share any personally identifying information publicly or with third-parties, except when required to by law.

+

Our website may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies.

+

You are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services.

+

Your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.

+

This policy is effective as of .

+ Back to Home +
+ +