diff --git a/src/App.css b/src/App.css index 9838c73..9ecf8f2 100644 --- a/src/App.css +++ b/src/App.css @@ -1,7 +1,7 @@ .todo-container { - max-width: 540px; + max-width: 560px; width: 100%; - margin: 60px auto; + margin: 50px auto; padding: 32px; background: var(--bg); border: 1px solid var(--border); @@ -21,88 +21,178 @@ .input-section { display: flex; - gap: 12px; - margin-bottom: 24px; + flex-direction: column; + gap: 16px; + margin-bottom: 28px; + background: var(--code-bg); + padding: 20px; + border-radius: 12px; + border: 1px solid var(--border); } -.input-section input { - flex: 1; - padding: 12px 16px; +.task-input-wrapper { + width: 100%; +} + +.task-input { + width: 100%; + padding: 14px 16px; font-size: 16px; border: 1px solid var(--border); border-radius: 8px; - background: var(--code-bg); + background: var(--bg); color: var(--text-h); outline: none; + box-sizing: border-box; transition: border-color 0.2s, box-shadow 0.2s; } -.input-section input:focus { +.task-input:focus, +.reminder-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); } -.input-section button { - padding: 12px 20px; - font-size: 16px; +.task-input.input-error { + border-color: #ef4444; + box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15); +} + +.reminder-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.reminder-label { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: var(--text-h); +} + +.label-icon { + font-size: 14px; +} + +.reminder-controls { + display: flex; + gap: 12px; + align-items: center; +} + +.reminder-input { + flex: 1; + padding: 12px 14px; + font-size: 14px; + font-family: inherit; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg); + color: var(--text-h); + outline: none; + box-sizing: border-box; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.add-btn { + padding: 12px 24px; + font-size: 15px; font-weight: 600; background: var(--accent); color: #ffffff; border: none; border-radius: 8px; cursor: pointer; + white-space: nowrap; transition: opacity 0.2s, transform 0.1s; } -.input-section button:hover { +.add-btn:hover { opacity: 0.9; } -.input-section button:active { +.add-btn:active { transform: scale(0.98); } +.error-message { + margin: 0; + color: #ef4444; + font-size: 14px; + font-weight: 500; +} + .todo-container ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; - gap: 10px; + gap: 12px; } .todo-container li { display: flex; align-items: center; justify-content: space-between; - padding: 12px 16px; + padding: 14px 16px; background: var(--code-bg); border: 1px solid var(--border); - border-radius: 8px; + border-radius: 10px; transition: background-color 0.2s, border-color 0.2s; } -.todo-container li span { +.task-content { flex: 1; + display: flex; + flex-direction: column; + gap: 6px; cursor: pointer; - color: var(--text-h); - font-size: 16px; user-select: none; - transition: color 0.2s, text-decoration 0.2s; - word-break: break-word; } -.todo-container li span:hover { +.task-text { + color: var(--text-h); + font-size: 16px; + font-weight: 500; + word-break: break-word; + transition: color 0.2s, text-decoration 0.2s; +} + +.task-content:hover .task-text { color: var(--accent); } -.todo-container li.completed span { +.task-timestamps { + display: flex; + flex-wrap: wrap; + gap: 12px; + font-size: 12px; + color: var(--text); + opacity: 0.85; +} + +.task-created, +.task-reminder { + display: inline-flex; + align-items: center; +} + +.todo-container li.completed .task-text { text-decoration: line-through; color: var(--text); opacity: 0.6; } -.todo-container li button { +.todo-container li.completed .task-timestamps { + text-decoration: line-through; + opacity: 0.4; +} + +.delete-btn { padding: 6px 12px; font-size: 14px; background: transparent; @@ -114,8 +204,22 @@ transition: background-color 0.2s, color 0.2s, border-color 0.2s; } -.todo-container li button:hover { +.delete-btn:hover { background: #ef4444; color: #ffffff; border-color: #ef4444; } + +@media (max-width: 520px) { + .reminder-controls { + flex-direction: column; + align-items: stretch; + } + .add-btn { + width: 100%; + } + .task-timestamps { + flex-direction: column; + gap: 4px; + } +} diff --git a/src/App.jsx b/src/App.jsx index 74a879d..81cc2b3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,56 +1,179 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import "./App.css"; function App() { const [task, setTask] = useState(""); + const [reminder, setReminder] = useState(""); const [tasks, setTasks] = useState([]); + const [error, setError] = useState(""); - const addTask = () => { - if (task.trim() === "") return; + const addTask = async () => { + if (task.trim() === "") { + setError("Please enter a task name."); + return; + } + + let reminderIso = null; + if (reminder) { + const selectedDate = new Date(reminder); + if (!isNaN(selectedDate.getTime())) { + reminderIso = selectedDate.toISOString(); + } + + // Requirement: Ask for notification permission only when setting a reminder + if ("Notification" in window && Notification.permission === "default") { + try { + await Notification.requestPermission(); + } catch (err) { + console.error("Notification permission error:", err); + } + } + } const newTask = { - text: task, + id: Date.now() + Math.random(), + text: task.trim(), completed: false, + createdAt: new Date().toISOString(), + reminder: reminderIso, + reminderFired: false, }; - setTasks([...tasks, newTask]); + setTasks((prev) => [...prev, newTask]); setTask(""); + setReminder(""); + setError(""); }; - const deleteTask = (index) => { - setTasks(tasks.filter((_, i) => i !== index)); + const deleteTask = (id) => { + setTasks((prev) => prev.filter((item) => item.id !== id)); }; - const toggleTask = (index) => { - setTasks( - tasks.map((item, i) => - i === index ? { ...item, completed: !item.completed } : item + const toggleTask = (id) => { + setTasks((prev) => + prev.map((item) => + item.id === id ? { ...item, completed: !item.completed } : item ) ); }; + useEffect(() => { + const interval = setInterval(() => { + const now = new Date().getTime(); + + setTasks((prevTasks) => { + let updated = false; + const nextTasks = prevTasks.map((item) => { + if ( + item.reminder && + !item.reminderFired && + !item.completed && + new Date(item.reminder).getTime() <= now + ) { + updated = true; + + if ("Notification" in window && Notification.permission === "granted") { + try { + new Notification("Task Reminder", { + body: `Reminder: ${item.text}`, + }); + } catch (e) { + console.error("Notification failed:", e); + } + } + + alert(`Reminder: ${item.text}`); + + return { ...item, reminderFired: true }; + } + return item; + }); + + return updated ? nextTasks : prevTasks; + }); + }, 3000); + + return () => clearInterval(interval); + }, []); + + const formatDate = (isoString) => { + if (!isoString) return ""; + const date = new Date(isoString); + if (isNaN(date.getTime())) return isoString; + + const day = date.getDate(); + const month = date.toLocaleString("en-US", { month: "short" }); + const year = date.getFullYear(); + const time = date.toLocaleString("en-US", { + hour: "2-digit", + minute: "2-digit", + hour12: true, + }); + + return `${day} ${month} ${year}, ${time}`; + }; + return (
{error}
}