medicare
This commit is contained in:
parent
098f31ce0a
commit
a65e92aba5
132
assets/css/custom.css
Normal file
132
assets/css/custom.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
250
assets/js/main.js
Normal file
250
assets/js/main.js
Normal file
@ -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 = '<div class="d-flex justify-content-center"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
||||||
|
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 = `<strong>High Risk Interaction:</strong> 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 = `<strong>No Major Interaction Found:</strong> No significant interaction was found between ${drugA} and ${drugB}. This is not a substitute for professional medical advice.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultDiv.innerHTML = `<div class="alert ${alertClass}">${resultMessage}</div>`;
|
||||||
|
}, 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 = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 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 = '<p class="text-muted">No medicines being tracked yet.</p>';
|
||||||
|
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 = `
|
||||||
|
<div>
|
||||||
|
<strong>${medicine.medicine_name}</strong>
|
||||||
|
<br>
|
||||||
|
<small class="expiry-date ${status.className}">${status.text}</small>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-outline-danger delete-medicine" data-id="${medicine.id}">Remove</button>
|
||||||
|
`;
|
||||||
|
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 = '<p class="text-danger">Could not load medicines.</p>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching medicines:', error);
|
||||||
|
medicineListContainer.innerHTML = '<p class="text-danger">Error loading medicines.</p>';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
60
contact.php
Normal file
60
contact.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/mail/MailService.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => 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.']);
|
||||||
|
}
|
||||||
33
db/migrate.php
Normal file
33
db/migrate.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$pdo->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() . "
|
||||||
|
");
|
||||||
|
}
|
||||||
|
?>
|
||||||
6
db/migrations/001_create_medicines_table.sql
Normal file
6
db/migrations/001_create_medicines_table.sql
Normal file
@ -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
|
||||||
|
);
|
||||||
351
index.php
351
index.php
@ -1,150 +1,217 @@
|
|||||||
<?php
|
<!DOCTYPE html>
|
||||||
declare(strict_types=1);
|
|
||||||
@ini_set('display_errors', '1');
|
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New Style</title>
|
<title>MediScan - Smart Medicine Management</title>
|
||||||
<?php
|
<meta name="description" content="MediScan: Effortlessly check drug interactions and expiry dates with real-time alerts for patients, pharmacists, and doctors.">
|
||||||
// Read project preview data from environment
|
<meta name="keywords" content="medication management, drug interaction checker, pill reminder, pharmacy app, healthcare technology, medicine scanner, expiry date tracker, patient safety, digital health, medical app, prescription management, drug alerts">
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
<!-- Open Graph / Facebook -->
|
||||||
?>
|
<meta property="og:type" content="website">
|
||||||
<?php if ($projectDescription): ?>
|
<meta property="og:title" content="MediScan - Smart Medicine Management">
|
||||||
<!-- Meta description -->
|
<meta property="og:description" content="MediScan: Effortlessly check drug interactions and expiry dates with real-time alerts for patients, pharmacists, and doctors.">
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
<meta property="og:image" content="https://project-screens.s3.amazonaws.com/screenshots/34616/app-hero-20251003-083323.png">
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
<!-- Twitter -->
|
||||||
<!-- Twitter meta tags -->
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
<meta name="twitter:title" content="MediScan - Smart Medicine Management">
|
||||||
<?php endif; ?>
|
<meta name="twitter:description" content="MediScan: Effortlessly check drug interactions and expiry dates with real-time alerts for patients, pharmacists, and doctors.">
|
||||||
<?php if ($projectImageUrl): ?>
|
<meta name="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34616/app-hero-20251003-083323.png">
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<!-- Twitter image -->
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<?php endif; ?>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Georgia:wght@700&display=swap" rel="stylesheet">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
|
||||||
<div class="card">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<div class="container">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<a class="navbar-brand" href="#">MediScan</a>
|
||||||
<span class="sr-only">Loading…</span>
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#about">About</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#expiry-tracker">Expiry Tracker</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header id="hero" class="hero">
|
||||||
|
<div class="container text-center text-white">
|
||||||
|
<h1 class="display-3">Effortless Medication Safety.</h1>
|
||||||
|
<p class="lead my-4">Scan, check, and manage your medications with confidence. Real-time interaction alerts and expiry tracking at your fingertips.</p>
|
||||||
|
<a href="#checker" class="btn btn-primary btn-lg">Check Interactions Now</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section id="checker" class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<h2 class="text-center mb-4">Drug Interaction Checker</h2>
|
||||||
|
<form id="interaction-form">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="drugA" class="form-label">Drug A</label>
|
||||||
|
<input type="text" class="form-control" id="drugA" placeholder="e.g., Warfarin" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="drugB" class="form-label">Drug B</label>
|
||||||
|
<input type="text" class="form-control" id="drugB" placeholder="e.g., Aspirin" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary">Check for Interactions</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="interaction-result" class="mt-4 d-none"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="features" class="py-5 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-5">Core Features</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Instant Scanning</h5>
|
||||||
|
<p class="card-text">Use your camera to scan medicine barcodes or packaging to instantly pull up details.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Interaction Alerts</h5>
|
||||||
|
<p class="card-text">Cross-reference multiple medications to receive clear warnings about potential drug interactions.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Expiry Tracking</h5>
|
||||||
|
<p class="card-text">Never wonder about an expiry date again. Get automatic reminders for your medicines.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="about" class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>About MediScan</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<img src="https://picsum.photos/seed/medabout/800/600" class="img-fluid rounded shadow" alt="A diverse group of healthcare professionals collaborating.">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="expiry-tracker" class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-5">Expiry Alert Tracker</h2>
|
||||||
|
<div class="row">
|
||||||
|
<!-- Add Medicine Form -->
|
||||||
|
<div class="col-lg-4 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="card-title mb-3">Add New Medicine</h5>
|
||||||
|
<form id="medicine-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="medicine_name" class="form-label">Medicine Name</label>
|
||||||
|
<input type="text" class="form-control" id="medicine_name" placeholder="e.g., Atorvastatin" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="expiry_date" class="form-label">Expiry Date</label>
|
||||||
|
<input type="date" class="form-control" id="expiry_date" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Add to Tracker</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Medicine List -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="card-title mb-3">Tracked Medicines</h5>
|
||||||
|
<div id="medicine-list-container">
|
||||||
|
<div class="d-flex justify-content-center"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="contact" class="py-5 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-4">Get In Touch</h2>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<form id="contact-form">
|
||||||
|
<div id="contact-alert" class="d-none"></div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="message" class="form-label">Message</label>
|
||||||
|
<textarea class="form-control" id="message" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Send Message</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="py-4 bg-dark text-white text-center">
|
||||||
|
<div class="container">
|
||||||
|
<p>© <?php echo date("Y"); ?> MediScan. All Rights Reserved. | <a href="/privacy.php" class="text-white">Privacy Policy</a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||||
|
<div id="contact-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto">MediScan</strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
<footer>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
45
medicines.php
Normal file
45
medicines.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$response = ['success' => 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);
|
||||||
|
?>
|
||||||
28
privacy.php
Normal file
28
privacy.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
// Basic privacy policy page stub
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Privacy Policy - MediScan</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p>This is a placeholder for the Privacy Policy.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>We don’t share any personally identifying information publicly or with third-parties, except when required to by law.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>This policy is effective as of <?php echo date("F j, Y"); ?>.</p>
|
||||||
|
<a href="/" class="btn btn-primary">Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user