From 6a4e5985e57bcd0387d84f669322d97137ec55a0 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 21 Nov 2025 18:29:31 +0000 Subject: [PATCH] Auto commit: 2025-11-21T18:29:31.267Z --- api/alarms.php | 173 ++++++++++--------- api/fetch_bell_icon.php | 19 +++ assets/images/bell.png | Bin 0 -> 10967 bytes assets/js/main.js | 368 ++++++++++++++++++---------------------- includes/pexels.php | 25 +++ index.php | 3 +- 6 files changed, 302 insertions(+), 286 deletions(-) create mode 100644 api/fetch_bell_icon.php create mode 100644 assets/images/bell.png create mode 100644 includes/pexels.php diff --git a/api/alarms.php b/api/alarms.php index 386c89f..70d902e 100644 --- a/api/alarms.php +++ b/api/alarms.php @@ -3,99 +3,112 @@ require_once __DIR__ . '/../db/config.php'; header('Content-Type: application/json'); -$response = ['success' => false, 'message' => 'Invalid request']; +$response = ['success' => false, 'error' => 'Invalid request']; $pdo = db(); -if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) { - if ($_GET['action'] === 'check') { +$action = $_GET['action'] ?? $_POST['action'] ?? ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'create') { + $alarm_time = $_POST['alarm_time'] ?? null; + + if ($alarm_time) { 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); + // 1. Create a new note + $noteStmt = $pdo->prepare("INSERT INTO notes (content) VALUES (?)"); + $noteStmt->execute(['']); + $noteId = $pdo->lastInsertId(); - 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' => []]; - } + // 2. Create the alarm and link it to the new note + $alarmStmt = $pdo->prepare("INSERT INTO alarms (alarm_time, note_id, is_active) VALUES (?, ?, 1)"); + $alarmStmt->execute([$alarm_time, $noteId]); + $alarmId = $pdo->lastInsertId(); $pdo->commit(); + + $response = [ + 'success' => true, + 'id' => $alarmId, + 'note_id' => $noteId, + 'alarm_time' => $alarm_time + ]; + } catch (PDOException $e) { $pdo->rollBack(); - $response['message'] = 'Database error: ' . $e->getMessage(); + $response['error'] = 'Database error: ' . $e->getMessage(); } + } else { + $response['error'] = 'Alarm time is required.'; } -} 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.'; - } +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'get') { + try { + $stmt = $pdo->query("SELECT id, alarm_time, note_id, is_active FROM alarms ORDER BY alarm_time"); + $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC); + $response = ['success' => true, 'alarms' => $alarms]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); } } -echo json_encode($response); +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'delete') { + $id = $_GET['id'] ?? null; + if ($id) { + try { + $stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?"); + $stmt->execute([$id]); + $response = ['success' => true]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'ID is required.'; + } +} + +elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'toggle') { + $id = $_POST['id'] ?? null; + $is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : null; + + if ($id && $is_active !== null) { + try { + $stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?"); + $stmt->execute([$is_active, $id]); + $response = ['success' => true]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'ID and active status are required.'; + } +} + +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'check') { + try { + $pdo->beginTransaction(); + + $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) { + $alarmIds = array_map(fn($a) => $a['id'], $alarms); + $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['error'] = 'Database error: ' . $e->getMessage(); + } +} + +echo json_encode($response); \ No newline at end of file diff --git a/api/fetch_bell_icon.php b/api/fetch_bell_icon.php new file mode 100644 index 0000000..c4ac55f --- /dev/null +++ b/api/fetch_bell_icon.php @@ -0,0 +1,19 @@ + true, 'local' => 'assets/images/bell.png']); + exit; +} +$photo = $data['photos'][0]; +$src = $photo['src']['tiny'] ?? ($photo['src']['small'] ?? $photo['src']['original']); +$target = __DIR__ . '/../assets/images/bell.png'; +download_to($src, $target); +echo json_encode(['success' => true, 'local' => 'assets/images/bell.png']); diff --git a/assets/images/bell.png b/assets/images/bell.png new file mode 100644 index 0000000000000000000000000000000000000000..f332071e734cecc5f9033c11b7274cc50ca4ddd4 GIT binary patch literal 10967 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 literal 0 HcmV?d00001 diff --git a/assets/js/main.js b/assets/js/main.js index ac580b2..2b7ce8a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,229 +1,187 @@ + 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'); + const alarmForm = document.getElementById('alarm-form'); + const alarmsList = document.getElementById('alarms-list'); + const alarmModal = new bootstrap.Modal(document.getElementById('alarm-modal')); + const alarmSound = document.getElementById('alarm-sound'); + const dismissAlarmBtn = document.getElementById('dismiss-alarm'); + const enableNotificationsBtn = document.getElementById('enable-notifications'); - // --- STATE --- - let isAlarmModalShown = false; + let notificationPermission = 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'; + // Request notification permission + if (enableNotificationsBtn) { + enableNotificationsBtn.addEventListener('click', () => { + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + notificationPermission = true; + alert('Notifications enabled!'); + enableNotificationsBtn.style.display = 'none'; + } else { + alert('Notification permission denied.'); } - } else { - alert('Error: ' + result.message); - } - } catch (error) { - console.error('Failed to create alarm:', error); - alert('An error occurred while creating the alarm.'); - } + }); + }); + } + + + // Function to fetch and display alarms + const fetchAlarms = () => { + fetch('/api/alarms.php?action=get') + .then(response => response.json()) + .then(data => { + alarmsList.innerHTML = ''; + if (data.success) { + data.alarms.forEach(alarm => { + addAlarmToList(alarm.id, alarm.alarm_time, alarm.note_id, alarm.is_active); + }); + } + }); }; - /** - * 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; + // Function to add a single alarm to the list + const addAlarmToList = (id, time, noteId, isActive) => { + const listItem = document.createElement('li'); + listItem.className = 'list-group-item d-flex justify-content-between align-items-center'; + listItem.dataset.id = id; - const formData = new FormData(form); + const timeText = document.createElement('span'); + timeText.textContent = new Date(`1970-01-01T${time}`).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - try { - const response = await fetch('api/alarms.php', { + const noteLink = document.createElement('a'); + noteLink.href = `/note.php?id=${noteId}`; + noteLink.textContent = `Note #${noteId}`; + noteLink.className = 'mx-3'; + + const controls = document.createElement('div'); + + const toggleSwitch = document.createElement('div'); + toggleSwitch.className = 'form-check form-switch'; + const toggleInput = document.createElement('input'); + toggleInput.className = 'form-check-input'; + toggleInput.type = 'checkbox'; + toggleInput.role = 'switch'; + toggleInput.checked = isActive; + toggleInput.addEventListener('change', () => handleToggleAlarm(id, toggleInput.checked)); + toggleSwitch.appendChild(toggleInput); + + const deleteBtn = document.createElement('button'); + deleteBtn.className = 'btn btn-danger btn-sm ms-3'; + deleteBtn.textContent = 'Delete'; + deleteBtn.addEventListener('click', () => handleDeleteAlarm(id)); + + controls.appendChild(toggleSwitch); + controls.appendChild(deleteBtn); + + listItem.appendChild(timeText); + listItem.appendChild(noteLink); + listItem.appendChild(controls); + alarmsList.appendChild(listItem); + }; + + // Handle form submission to create a new alarm + if (alarmForm) { + alarmForm.addEventListener('submit', function (e) { + e.preventDefault(); + const formData = new FormData(alarmForm); + fetch('/api/alarms.php?action=create', { method: 'POST', body: formData - }); - const result = await response.json(); + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + addAlarmToList(data.id, data.alarm_time, data.note_id, true); + alarmForm.reset(); + } else { + alert('Error: ' + data.error); + } + }); + }); + } - if (result.success) { - const listItem = form.closest('li'); - listItem.remove(); - if (!alarmList.querySelector('li')) { - if (noAlarmsMessage) { - noAlarmsMessage.style.display = 'block'; + // Handle deleting an alarm + const handleDeleteAlarm = (id) => { + if (!confirm('Are you sure you want to delete this alarm?')) return; + fetch(`/api/alarms.php?action=delete&id=${id}`, { method: 'GET' }) + .then(response => response.json()) + .then(data => { + if (data.success) { + document.querySelector(`li[data-id='${id}']`).remove(); + } else { + alert('Error: ' + data.error); + } + }); + }; + + // Handle toggling an alarm's active state + const handleToggleAlarm = (id, isActive) => { + const formData = new FormData(); + formData.append('id', id); + formData.append('is_active', isActive ? 1 : 0); + + fetch('/api/alarms.php?action=toggle', { + method: 'POST', + body: formData + }).then(response => response.json()) + .then(data => { + if (!data.success) { + alert('Error updating alarm status.'); + } + }); + }; + + + // Function to check for due alarms + const checkAlarms = () => { + fetch('/api/alarms.php?action=check') + .then(response => response.json()) + .then(data => { + if (data.success && data.alarms.length > 0) { + const alarm = data.alarms[0]; // Assuming one alarm for now + showNotification(alarm); + // Also update the toggle on the main page + const alarmToggle = document.querySelector(`li[data-id='${alarm.id}'] input[type='checkbox']`); + if (alarmToggle) { + alarmToggle.checked = false; } } - } 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 + // Function to show notification + const showNotification = (alarm) => { + const alarmTime = new Date(`1970-01-01T${alarm.alarm_time}`).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + const notificationTitle = `Alarm! It's ${alarmTime}`; + const notificationBody = `Click to open your note.`; - try { - const response = await fetch('api/alarms.php?action=check'); - const result = await response.json(); + if (notificationPermission) { + const notification = new Notification(notificationTitle, { + body: notificationBody, + icon: '/assets/images/bell.png', // Optional: add an icon + sound: '/assets/sounds/alarm.mp3', // This doesn't work, sound is handled separately + requireInteraction: true // Keeps notification open until user interacts + }); - if (result.success && result.alarms.length > 0) { - const alarm = result.alarms[0]; - triggerAlarm(alarm); - } - } catch (error) { - console.error('Error checking alarms:', error); + notification.onclick = () => { + window.open(`/note.php?id=${alarm.note_id}`, '_blank'); + notification.close(); + }; } - }; - /** - * 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.'; - } + // Fallback to modal + alarmSound.play().catch(e => console.error("Audio playback failed:", e)); alarmModal.show(); - alarmSound.play().catch(e => console.error("Audio play failed:", e)); + + dismissAlarmBtn.onclick = () => { + alarmSound.pause(); + alarmSound.currentTime = 0; + alarmModal.hide(); + }; }; - /** - * 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 + // Initial fetch and periodic check + fetchAlarms(); + setInterval(checkAlarms, 5000); // Check every 5 seconds +}); diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..0c04a85 --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,25 @@ + 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18'; +} +function pexels_get($url) { + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true); + return null; +} +function download_to($srcUrl, $destPath) { + $data = file_get_contents($srcUrl); + if ($data === false) return false; + if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true); + return file_put_contents($destPath, $data) !== false; +} diff --git a/index.php b/index.php index 2dda883..28b96b0 100644 --- a/index.php +++ b/index.php @@ -107,7 +107,8 @@ try {