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 {
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;
}
}

View File

@@ -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 (
<div className="todo-container">
<h1>To-Do List</h1>
<div className="input-section">
<input
type="text"
placeholder="Enter a task..."
value={task}
onChange={(e) => setTask(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTask()}
/>
<div className="task-input-wrapper">
<input
type="text"
className={`task-input ${error ? "input-error" : ""}`}
placeholder="Enter a task..."
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>
<ul>
{tasks.map((item, index) => (
<li key={index} className={item.completed ? "completed" : ""}>
<span onClick={() => toggleTask(index)}>{item.text}</span>
{tasks.map((item) => (
<li key={item.id} className={item.completed ? "completed" : ""}>
<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>
))}
</ul>