diff --git a/api/alarms.php b/api/alarms.php
new file mode 100644
index 0000000..386c89f
--- /dev/null
+++ b/api/alarms.php
@@ -0,0 +1,101 @@
+ false, 'message' => 'Invalid request'];
+$pdo = db();
+
+if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
+ if ($_GET['action'] === 'check') {
+ try {
+ $pdo->beginTransaction();
+
+ // Find active alarms that are due and lock the rows
+ $stmt = $pdo->prepare("SELECT * FROM alarms WHERE alarm_time <= CURTIME() AND is_active = 1 FOR UPDATE");
+ $stmt->execute();
+ $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ if ($alarms) {
+ // Deactivate the found alarms to prevent them from ringing again
+ $alarmIds = array_map(function($alarm) {
+ return $alarm['id'];
+ }, $alarms);
+
+ if (!empty($alarmIds)) {
+ $placeholders = implode(',', array_fill(0, count($alarmIds), '?'));
+ $updateStmt = $pdo->prepare("UPDATE alarms SET is_active = 0 WHERE id IN ($placeholders)");
+ $updateStmt->execute($alarmIds);
+ }
+
+ $response = ['success' => true, 'alarms' => $alarms];
+ } else {
+ $response = ['success' => true, 'alarms' => []];
+ }
+
+ $pdo->commit();
+ } catch (PDOException $e) {
+ $pdo->rollBack();
+ $response['message'] = 'Database error: ' . $e->getMessage();
+ }
+ }
+} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ // Simple routing based on a POST field
+ $action = $_POST['action'] ?? '';
+
+ if ($action === 'create') {
+ $alarm_time = $_POST['alarm_time'] ?? null;
+ $label = $_POST['label'] ?? '';
+
+ if ($alarm_time) {
+ try {
+ $stmt = $pdo->prepare("INSERT INTO alarms (alarm_time, label) VALUES (?, ?)");
+ $stmt->execute([$alarm_time, $label]);
+ $response = ['success' => true, 'message' => 'Alarm created successfully.', 'id' => $pdo->lastInsertId()];
+ } catch (PDOException $e) {
+ $response['message'] = 'Database error: ' . $e->getMessage();
+ }
+ } else {
+ $response['message'] = 'Alarm time is required.';
+ }
+ } elseif ($action === 'delete') {
+ $alarm_id = $_POST['alarm_id'] ?? null;
+
+ if ($alarm_id) {
+ try {
+ $stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?");
+ $stmt->execute([$alarm_id]);
+ if ($stmt->rowCount()) {
+ $response = ['success' => true, 'message' => 'Alarm deleted successfully.'];
+ } else {
+ $response['message'] = 'Alarm not found.';
+ }
+ } catch (PDOException $e) {
+ $response['message'] = 'Database error: ' . $e->getMessage();
+ }
+ } else {
+ $response['message'] = 'Alarm ID is required.';
+ }
+ } elseif ($action === 'toggle') {
+ $alarm_id = $_POST['alarm_id'] ?? null;
+ $is_active = $_POST['is_active'] ?? null;
+
+ if ($alarm_id && $is_active !== null) {
+ try {
+ $stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?");
+ $stmt->execute([$is_active, $alarm_id]);
+ if ($stmt->rowCount()) {
+ $response = ['success' => true, 'message' => 'Alarm status updated.'];
+ } else {
+ $response['message'] = 'Alarm not found or status unchanged.';
+ }
+ } catch (PDOException $e) {
+ $response['message'] = 'Database error: ' . $e->getMessage();
+ }
+ } else {
+ $response['message'] = 'Alarm ID and active status are required.';
+ }
+ }
+}
+
+echo json_encode($response);
diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..44b7841
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,80 @@
+/* General Body Styles */
+body {
+ background-color: #F4F7F6;
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ color: #333;
+}
+
+/* Header Styles */
+.header-gradient {
+ background: linear-gradient(90deg, #4A90E2, #50E3C2);
+ color: white;
+ padding: 1rem 0;
+}
+
+/* Card Styles */
+.card {
+ border-radius: 0.5rem;
+ border: none;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+/* Button Styles */
+.btn-primary {
+ background-color: #4A90E2;
+ border-color: #4A90E2;
+ border-radius: 0.5rem;
+ padding: 0.75rem 1.5rem;
+ font-weight: 500;
+}
+
+.btn-primary:hover {
+ background-color: #3a7ac8;
+ border-color: #3a7ac8;
+}
+
+/* Form Styles */
+.form-control {
+ border-radius: 0.5rem;
+}
+
+.form-control:focus {
+ border-color: #4A90E2;
+ box-shadow: 0 0 0 0.2rem rgba(74, 144, 226, 0.25);
+}
+
+/* Toast Notification */
+.toast-container {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ z-index: 1055;
+}
+
+/* Alarm List Styles */
+#alarmList .list-group-item {
+ transition: background-color 0.2s ease-in-out;
+}
+
+#alarmList .list-group-item:hover {
+ background-color: #f8f9fa;
+}
+
+#alarmList .btn-outline-danger {
+ border: none;
+}
+
+/* Alarm Modal Styles */
+#alarmModal .modal-content {
+ border-radius: 1rem;
+}
+
+#alarmModal .feather-lg {
+ width: 80px;
+ height: 80px;
+ stroke-width: 1.5;
+}
+
+.modal-backdrop.show {
+ opacity: 0.8;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..ac580b2
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,229 @@
+document.addEventListener('DOMContentLoaded', function () {
+ // --- ELEMENTS ---
+ const createAlarmForm = document.getElementById('createAlarmForm');
+ const alarmList = document.getElementById('alarmList');
+ const noAlarmsMessage = document.getElementById('noAlarmsMessage');
+ const alarmModalEl = document.getElementById('alarmModal');
+ const alarmModal = new bootstrap.Modal(alarmModalEl);
+ const dismissAlarmBtn = document.getElementById('dismissAlarmBtn');
+ const alarmSound = document.getElementById('alarmSound');
+ const alarmModalMessage = document.getElementById('alarmModalMessage');
+
+ // --- STATE ---
+ let isAlarmModalShown = false;
+
+ // --- FUNCTIONS ---
+
+ /**
+ * Handles the submission of the create alarm form.
+ */
+ const handleCreateAlarm = async (e) => {
+ e.preventDefault();
+ const timeInput = document.getElementById('alarmTime');
+ const labelInput = document.getElementById('alarmLabel');
+
+ const formData = new FormData();
+ formData.append('action', 'create');
+ formData.append('alarm_time', timeInput.value);
+ formData.append('label', labelInput.value);
+
+ try {
+ const response = await fetch('api/alarms.php', {
+ method: 'POST',
+ body: formData
+ });
+ const result = await response.json();
+
+ if (result.success) {
+ addAlarmToList(result.id, timeInput.value, labelInput.value);
+ timeInput.value = '';
+ labelInput.value = '';
+ if (noAlarmsMessage) {
+ noAlarmsMessage.style.display = 'none';
+ }
+ } else {
+ alert('Error: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Failed to create alarm:', error);
+ alert('An error occurred while creating the alarm.');
+ }
+ };
+
+ /**
+ * Handles the click on a delete alarm form.
+ */
+ const handleDeleteAlarm = async (e) => {
+ if (!e.target.closest('.delete-alarm-form')) return;
+ e.preventDefault();
+
+ const form = e.target.closest('.delete-alarm-form');
+ const alarmId = form.querySelector('input[name="alarm_id"]').value;
+
+ if (!confirm('Are you sure you want to delete this alarm?')) return;
+
+ const formData = new FormData(form);
+
+ try {
+ const response = await fetch('api/alarms.php', {
+ method: 'POST',
+ body: formData
+ });
+ const result = await response.json();
+
+ if (result.success) {
+ const listItem = form.closest('li');
+ listItem.remove();
+ if (!alarmList.querySelector('li')) {
+ if (noAlarmsMessage) {
+ noAlarmsMessage.style.display = 'block';
+ }
+ }
+ } else {
+ alert('Error: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Failed to delete alarm:', error);
+ alert('An error occurred while deleting the alarm.');
+ }
+ };
+
+ /**
+ * Adds a new alarm item to the DOM.
+ */
+ const addAlarmToList = (id, time, label, isActive = true) => {
+ const date = new Date(`1970-01-01T${time}`);
+ const formattedTime = date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
+
+ const li = document.createElement('li');
+ li.className = 'list-group-item d-flex justify-content-between align-items-center';
+ li.dataset.id = id;
+ li.innerHTML = `
+
+
+
+
+
+
+ ${formattedTime}
+ ${escapeHTML(label)}
+
+
+
+ `;
+ alarmList.appendChild(li);
+ feather.replace();
+ };
+
+ /**
+ * Handles toggling the active state of an alarm.
+ */
+ const handleToggleAlarm = async (e) => {
+ if (!e.target.classList.contains('toggle-alarm-switch')) return;
+
+ const switchEl = e.target;
+ const listItem = switchEl.closest('li');
+ const alarmId = listItem.dataset.id;
+ const isActive = switchEl.checked ? 1 : 0;
+
+ const formData = new FormData();
+ formData.append('action', 'toggle');
+ formData.append('alarm_id', alarmId);
+ formData.append('is_active', isActive);
+
+ try {
+ const response = await fetch('api/alarms.php', {
+ method: 'POST',
+ body: formData
+ });
+ const result = await response.json();
+
+ if (!result.success) {
+ alert('Error: ' + result.message);
+ // Revert the switch on failure
+ switchEl.checked = !switchEl.checked;
+ }
+ } catch (error) {
+ console.error('Failed to toggle alarm:', error);
+ alert('An error occurred while updating the alarm.');
+ // Revert the switch on failure
+ switchEl.checked = !switchEl.checked;
+ }
+ };
+
+ /**
+ * Checks the server for any due alarms.
+ */
+ const checkAlarms = async () => {
+ if (isAlarmModalShown) return; // Don't check if an alarm is already ringing
+
+ try {
+ const response = await fetch('api/alarms.php?action=check');
+ const result = await response.json();
+
+ if (result.success && result.alarms.length > 0) {
+ const alarm = result.alarms[0];
+ triggerAlarm(alarm);
+ }
+ } catch (error) {
+ console.error('Error checking alarms:', error);
+ }
+ };
+
+ /**
+ * Triggers the visual and audible alarm.
+ */
+ const triggerAlarm = (alarm) => {
+ isAlarmModalShown = true;
+ if (alarm.label) {
+ alarmModalMessage.textContent = alarm.label;
+ } else {
+ alarmModalMessage.textContent = 'Time to write your notes.';
+ }
+ alarmModal.show();
+ alarmSound.play().catch(e => console.error("Audio play failed:", e));
+ };
+
+ /**
+ * Dismisses the alarm and redirects to the note page.
+ */
+ const dismissAlarm = () => {
+ alarmSound.pause();
+ alarmSound.currentTime = 0;
+ alarmModal.hide();
+ isAlarmModalShown = false;
+
+ const today = new Date();
+ const dateString = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
+ window.location.href = `note.php?date=${dateString}`;
+ };
+
+ const escapeHTML = (str) => {
+ const p = document.createElement('p');
+ p.appendChild(document.createTextNode(str));
+ return p.innerHTML;
+ }
+
+ // --- EVENT LISTENERS ---
+ if (createAlarmForm) {
+ createAlarmForm.addEventListener('submit', handleCreateAlarm);
+ }
+
+ if (alarmList) {
+ alarmList.addEventListener('click', handleDeleteAlarm);
+ alarmList.addEventListener('change', handleToggleAlarm);
+ }
+
+ if (dismissAlarmBtn) {
+ dismissAlarmBtn.addEventListener('click', dismissAlarm);
+ }
+
+ // --- INITIALIZATION ---
+ setInterval(checkAlarms, 5000); // Check for alarms every 5 seconds
+});
\ No newline at end of file
diff --git a/db/config.php b/db/config.php
index 43208bc..d19bf1f 100644
--- a/db/config.php
+++ b/db/config.php
@@ -15,3 +15,34 @@ function db() {
}
return $pdo;
}
+
+function run_migrations() {
+ $pdo = db();
+ // Create migrations table if it doesn't exist
+ $pdo->exec("CREATE TABLE IF NOT EXISTS migrations (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ migration VARCHAR(255) NOT NULL UNIQUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )");
+
+ // Get all run migrations
+ $run_migrations_stmt = $pdo->query("SELECT migration FROM migrations");
+ $run_migrations = $run_migrations_stmt->fetchAll(PDO::FETCH_COLUMN);
+
+ // Get all migration files
+ $migration_files = glob(__DIR__ . '/migrations/*.sql');
+
+ foreach ($migration_files as $file) {
+ $migration_name = basename($file);
+ if (!in_array($migration_name, $run_migrations)) {
+ $sql = file_get_contents($file);
+ $pdo->exec($sql);
+ $stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
+ $stmt->execute([$migration_name]);
+ }
+ }
+}
+
+// Run migrations on every request for simplicity in this context.
+// For production, this should be a separate script.
+run_migrations();
diff --git a/db/migrations/001_create_notes_table.sql b/db/migrations/001_create_notes_table.sql
new file mode 100644
index 0000000..8026ca4
--- /dev/null
+++ b/db/migrations/001_create_notes_table.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS notes (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ note_date DATE NOT NULL UNIQUE,
+ content TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
\ No newline at end of file
diff --git a/db/migrations/002_create_alarms_table.sql b/db/migrations/002_create_alarms_table.sql
new file mode 100644
index 0000000..01ccd8b
--- /dev/null
+++ b/db/migrations/002_create_alarms_table.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS alarms (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ alarm_time TIME NOT NULL,
+ label VARCHAR(255) NULL,
+ is_active BOOLEAN DEFAULT TRUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
diff --git a/index.php b/index.php
index 7205f3d..2dda883 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,130 @@
query("SELECT * FROM alarms ORDER BY alarm_time ASC");
+ $alarms = $stmt->fetchAll();
+} catch (PDOException $e) {
+ // Handle error gracefully
+ $alarms = [];
+ error_log("Error fetching alarms: " . $e->getMessage());
+}
?>
-
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Alarm & Note App
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ No alarms set yet.
+
+
+
+ -
+
+
+ >
+
+
+
+ = htmlspecialchars(date("g:i A", strtotime($alarm['alarm_time']))) ?>
+ = htmlspecialchars($alarm['label']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Alarm!
+
Time to write your notes.
+
+
+
+
-
-
+
+
+
+
+
+
+
+
diff --git a/note.php b/note.php
new file mode 100644
index 0000000..96155c6
--- /dev/null
+++ b/note.php
@@ -0,0 +1,84 @@
+prepare(
+ "INSERT INTO notes (note_date, content) VALUES (?, ?)
+ ON DUPLICATE KEY UPDATE content = VALUES(content)"
+ );
+ $stmt->execute([$date_to_save, $content]);
+ $message = 'Note saved successfully!';
+ } catch (PDOException $e) {
+ // In a real app, log this error instead of showing it to the user
+ $message = 'Error saving note: ' . $e->getMessage();
+ }
+}
+
+// Fetch existing note for the date
+$stmt = $pdo->prepare("SELECT content FROM notes WHERE note_date = ?");
+$stmt->execute([$note_date]);
+$note = $stmt->fetch();
+if ($note) {
+ $note_content = $note['content'];
+}
+
+$formatted_date = date("l, F j, Y", strtotime($note_date));
+
+?>
+
+
+
+
+
+
Note for = htmlspecialchars($formatted_date) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($formatted_date) ?>
+
+
+
+ = htmlspecialchars($message) ?>
+
+
+
+
+
+
+
+
+
+