Add task date and reminder feature

This commit is contained in:
ANANYA MAHESHWARI
2026-08-20 20:33:19 +05:30
parent 37228a0971
commit 861f3cac43
2 changed files with 276 additions and 49 deletions

View File

@@ -1,7 +1,7 @@
.todo-container { .todo-container {
max-width: 540px; max-width: 560px;
width: 100%; width: 100%;
margin: 60px auto; margin: 50px auto;
padding: 32px; padding: 32px;
background: var(--bg); background: var(--bg);
border: 1px solid var(--border); border: 1px solid var(--border);
@@ -21,88 +21,178 @@
.input-section { .input-section {
display: flex; display: flex;
gap: 12px; flex-direction: column;
margin-bottom: 24px; gap: 16px;
margin-bottom: 28px;
background: var(--code-bg);
padding: 20px;
border-radius: 12px;
border: 1px solid var(--border);
} }
.input-section input { .task-input-wrapper {
flex: 1; width: 100%;
padding: 12px 16px; }
.task-input {
width: 100%;
padding: 14px 16px;
font-size: 16px; font-size: 16px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
background: var(--code-bg); background: var(--bg);
color: var(--text-h); color: var(--text-h);
outline: none; outline: none;
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
} }
.input-section input:focus { .task-input:focus,
.reminder-input:focus {
border-color: var(--accent); border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-bg); box-shadow: 0 0 0 3px var(--accent-bg);
} }
.input-section button { .task-input.input-error {
padding: 12px 20px; border-color: #ef4444;
font-size: 16px; 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; font-weight: 600;
background: var(--accent); background: var(--accent);
color: #ffffff; color: #ffffff;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
white-space: nowrap;
transition: opacity 0.2s, transform 0.1s; transition: opacity 0.2s, transform 0.1s;
} }
.input-section button:hover { .add-btn:hover {
opacity: 0.9; opacity: 0.9;
} }
.input-section button:active { .add-btn:active {
transform: scale(0.98); transform: scale(0.98);
} }
.error-message {
margin: 0;
color: #ef4444;
font-size: 14px;
font-weight: 500;
}
.todo-container ul { .todo-container ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 12px;
} }
.todo-container li { .todo-container li {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 12px 16px; padding: 14px 16px;
background: var(--code-bg); background: var(--code-bg);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 10px;
transition: background-color 0.2s, border-color 0.2s; transition: background-color 0.2s, border-color 0.2s;
} }
.todo-container li span { .task-content {
flex: 1; flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
cursor: pointer; cursor: pointer;
color: var(--text-h);
font-size: 16px;
user-select: none; 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); 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; text-decoration: line-through;
color: var(--text); color: var(--text);
opacity: 0.6; 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; padding: 6px 12px;
font-size: 14px; font-size: 14px;
background: transparent; background: transparent;
@@ -114,8 +204,22 @@
transition: background-color 0.2s, color 0.2s, border-color 0.2s; transition: background-color 0.2s, color 0.2s, border-color 0.2s;
} }
.todo-container li button:hover { .delete-btn:hover {
background: #ef4444; background: #ef4444;
color: #ffffff; color: #ffffff;
border-color: #ef4444; 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;
}
}

View File

@@ -1,56 +1,179 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import "./App.css"; import "./App.css";
function App() { function App() {
const [task, setTask] = useState(""); const [task, setTask] = useState("");
const [reminder, setReminder] = useState("");
const [tasks, setTasks] = useState([]); const [tasks, setTasks] = useState([]);
const [error, setError] = useState("");
const addTask = () => { const addTask = async () => {
if (task.trim() === "") return; 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 = { const newTask = {
text: task, id: Date.now() + Math.random(),
text: task.trim(),
completed: false, completed: false,
createdAt: new Date().toISOString(),
reminder: reminderIso,
reminderFired: false,
}; };
setTasks([...tasks, newTask]); setTasks((prev) => [...prev, newTask]);
setTask(""); setTask("");
setReminder("");
setError("");
}; };
const deleteTask = (index) => { const deleteTask = (id) => {
setTasks(tasks.filter((_, i) => i !== index)); setTasks((prev) => prev.filter((item) => item.id !== id));
}; };
const toggleTask = (index) => { const toggleTask = (id) => {
setTasks( setTasks((prev) =>
tasks.map((item, i) => prev.map((item) =>
i === index ? { ...item, completed: !item.completed } : 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 ( return (
<div className="todo-container"> <div className="todo-container">
<h1>To-Do List</h1> <h1>To-Do List</h1>
<div className="input-section"> <div className="input-section">
<input <div className="task-input-wrapper">
type="text" <input
placeholder="Enter a task..." type="text"
value={task} className={`task-input ${error ? "input-error" : ""}`}
onChange={(e) => setTask(e.target.value)} placeholder="Enter a task..."
onKeyDown={(e) => e.key === "Enter" && addTask()} value={task}
/> onChange={(e) => {
setTask(e.target.value);
if (error) setError("");
}}
onKeyDown={(e) => e.key === "Enter" && addTask()}
/>
</div>
<button onClick={addTask}>Add</button> <div className="reminder-section">
<label className="reminder-label">
<span className="label-icon">📅</span> Reminder (Optional)
</label>
<div className="reminder-controls">
<input
type="datetime-local"
className="reminder-input"
value={reminder}
onChange={(e) => setReminder(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTask()}
/>
<button className="add-btn" onClick={addTask}>
Add Task
</button>
</div>
</div>
{error && <p className="error-message">{error}</p>}
</div> </div>
<ul> <ul>
{tasks.map((item, index) => ( {tasks.map((item) => (
<li key={index} className={item.completed ? "completed" : ""}> <li key={item.id} className={item.completed ? "completed" : ""}>
<span onClick={() => toggleTask(index)}>{item.text}</span> <div className="task-content" onClick={() => toggleTask(item.id)}>
<span className="task-text">{item.text}</span>
<button onClick={() => deleteTask(index)}>Delete</button> <div className="task-timestamps">
<span className="task-created">
Added: {formatDate(item.createdAt)}
</span>
<span className="task-reminder">
{item.reminder
? `🔔 Reminder: ${formatDate(item.reminder)}`
: "No reminder set"}
</span>
</div>
</div>
<button className="delete-btn" onClick={() => deleteTask(item.id)}>
Delete
</button>
</li> </li>
))} ))}
</ul> </ul>