diff --git a/backend/models/Task.js b/backend/models/Task.js
new file mode 100644
index 0000000..dfc56ba
--- /dev/null
+++ b/backend/models/Task.js
@@ -0,0 +1,16 @@
+const mongoose = require('mongoose');
+
+const TaskSchema = new mongoose.Schema(
+ {
+ title: { type: String, required: true, trim: true },
+ description: { type: String, default: '' },
+ status: { type: String, enum: ['pending', 'completed'], default: 'pending' },
+ deadline: { type: Date },
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
+ },
+ { timestamps: true }
+);
+
+module.exports = mongoose.model('Task', TaskSchema);
+
+
diff --git a/backend/routes/tasks.route.js b/backend/routes/tasks.route.js
new file mode 100644
index 0000000..9e49627
--- /dev/null
+++ b/backend/routes/tasks.route.js
@@ -0,0 +1,87 @@
+const express = require('express');
+const router = express.Router();
+const auth = require('../middleware/auth');
+const Task = require('../models/Task');
+
+// POST /api/tasks - Create a new task
+router.post('/', auth, async (req, res) => {
+ try {
+ const { title, description, status, deadline } = req.body;
+ if (!title || !title.trim()) {
+ return res.status(400).json({ errors: [{ msg: 'Title is required' }] });
+ }
+ const task = new Task({
+ title: title.trim(),
+ description: description || '',
+ status: status === 'completed' ? 'completed' : 'pending',
+ deadline: deadline ? new Date(deadline) : undefined,
+ userId: req.user.id,
+ });
+ const saved = await task.save();
+ return res.status(201).json(saved);
+ } catch (err) {
+ console.error('Create task error:', err);
+ return res.status(500).json({ errors: [{ msg: 'Server error' }] });
+ }
+});
+
+// GET /api/tasks - Get tasks for the authenticated user
+router.get('/', auth, async (req, res) => {
+ try {
+ const tasks = await Task.find({ userId: req.user.id }).sort({ createdAt: -1 });
+ return res.json(tasks);
+ } catch (err) {
+ console.error('Fetch tasks error:', err);
+ return res.status(500).json({ errors: [{ msg: 'Server error' }] });
+ }
+});
+
+// PUT /api/tasks/:id - Update a task (only owner)
+router.put('/:id', auth, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const updates = {};
+ if (typeof req.body.title === 'string') updates.title = req.body.title.trim();
+ if (typeof req.body.description === 'string') updates.description = req.body.description;
+ if (typeof req.body.status === 'string' && ['pending','completed'].includes(req.body.status)) updates.status = req.body.status;
+ if (typeof req.body.deadline !== 'undefined') updates.deadline = req.body.deadline ? new Date(req.body.deadline) : null;
+ // Never allow changing userId via API
+
+ const task = await Task.findOne({ _id: id, userId: req.user.id });
+ if (!task) {
+ return res.status(404).json({ errors: [{ msg: 'Task not found' }] });
+ }
+
+ Object.assign(task, updates);
+ const saved = await task.save();
+ return res.json(saved);
+ } catch (err) {
+ console.error('Update task error:', err);
+ if (err.name === 'CastError') {
+ return res.status(400).json({ errors: [{ msg: 'Invalid task id' }] });
+ }
+ return res.status(500).json({ errors: [{ msg: 'Server error' }] });
+ }
+});
+
+// DELETE /api/tasks/:id - Delete a task (only owner)
+router.delete('/:id', auth, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const task = await Task.findOneAndDelete({ _id: id, userId: req.user.id });
+ if (!task) {
+ return res.status(404).json({ errors: [{ msg: 'Task not found' }] });
+ }
+ return res.json({ msg: 'Task deleted' });
+ } catch (err) {
+ console.error('Delete task error:', err);
+ if (err.name === 'CastError') {
+ return res.status(400).json({ errors: [{ msg: 'Invalid task id' }] });
+ }
+ return res.status(500).json({ errors: [{ msg: 'Server error' }] });
+ }
+});
+
+module.exports = router;
+
+
diff --git a/backend/server.js b/backend/server.js
index 2c8683a..749be65 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -63,6 +63,7 @@ app.use("/api/github", githubRoute);
app.use("/api/auth", authMiddleware, require("./routes/auth"));
app.use("/api/profile", generalMiddleware, require("./routes/profile"));
app.use("/api/contact", generalMiddleware, contactRouter);
+app.use("/api/tasks", require("./routes/tasks.route"));
// Default route
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 3012404..7aa514f 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -23,6 +23,7 @@ import ProtectedRoute from "./Components/auth/ProtectedRoute";
import Dashboard from "./Components/Dashboard";
import FAQ from "./Components/FAQ";
import Pomodoro from "./Components/DashBoard/Pomodoro";
+import Todo from "./Components/DashBoard/Todo";
import { ArrowUp } from "lucide-react";
import GitHubProfile from "./Components/GitHubProfile";
import LeetCode from "./Components/DashBoard/LeetCode";
@@ -131,6 +132,7 @@ function App() {
/>
} />
} />
+ } />
} />
} />
} />
diff --git a/frontend/src/Components/DashBoard/Todo.jsx b/frontend/src/Components/DashBoard/Todo.jsx
new file mode 100644
index 0000000..11707ef
--- /dev/null
+++ b/frontend/src/Components/DashBoard/Todo.jsx
@@ -0,0 +1,319 @@
+import React, { useEffect, useMemo, useState } from "react";
+import Topbar from "./Topbar";
+import BackButton from "../ui/backbutton";
+import { Plus, Pencil, Trash2, CheckCircle2, Circle, Filter } from "lucide-react";
+
+// Mock-only UI for GSSOC To-Do page. Uses local state; backend wiring will follow.
+export default function Todo() {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const API = `${import.meta.env.VITE_API_URL}/api/tasks`;
+
+ const [form, setForm] = useState({ title: "", description: "", deadline: "" });
+ const [editingId, setEditingId] = useState(null);
+ const [filter, setFilter] = useState("all"); // all | upcoming | completed
+ const [showModal, setShowModal] = useState(false);
+ const [modalData, setModalData] = useState({ title: "", description: "", status: "pending", deadline: "" });
+
+ const pendingTasks = useMemo(() => tasks.filter(t => t.status === "pending"), [tasks]);
+ const completedTasks = useMemo(() => tasks.filter(t => t.status === "completed"), [tasks]);
+
+ const filtered = (items) => {
+ if (filter === "completed") return items.filter(t => t.status === "completed");
+ if (filter === "upcoming") return items.filter(t => t.status === "pending").sort((a,b)=> new Date(a.deadline) - new Date(b.deadline));
+ return items;
+ };
+
+ // Load tasks on mount
+ useEffect(() => {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ setLoading(false);
+ return;
+ }
+ fetch(API, { headers: { "x-auth-token": token } })
+ .then(async (res) => {
+ const data = await res.json();
+ if (!res.ok) throw new Error(data?.errors?.[0]?.msg || "Failed to fetch tasks");
+ setTasks(data);
+ })
+ .catch((e) => console.error(e))
+ .finally(() => setLoading(false));
+ }, [API]);
+
+ function resetForm() {
+ setForm({ title: "", description: "", deadline: "" });
+ setModalData({ title: "", description: "", status: "pending", deadline: "" });
+ setEditingId(null);
+ }
+
+ function openAddModal() {
+ setModalData({ title: "", description: "", status: "pending", deadline: "" });
+ setEditingId(null);
+ setShowModal(true);
+ }
+
+ function handleSubmitModal(e) {
+ e.preventDefault();
+ if (!modalData.title.trim()) return;
+ const token = localStorage.getItem("token");
+ if (editingId) {
+ const taskId = editingId;
+ fetch(`${API}/${taskId}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json", "x-auth-token": token },
+ body: JSON.stringify({
+ title: modalData.title,
+ description: modalData.description,
+ status: modalData.status,
+ deadline: modalData.deadline || null,
+ })
+ })
+ .then(async (res) => {
+ const data = await res.json();
+ if (!res.ok) throw new Error(data?.errors?.[0]?.msg || "Failed to update task");
+ setTasks(prev => prev.map(t => (t._id || t.id) === taskId ? data : t));
+ setShowModal(false);
+ resetForm();
+ })
+ .catch((e) => console.error(e));
+ } else {
+ fetch(API, {
+ method: "POST",
+ headers: { "Content-Type": "application/json", "x-auth-token": token },
+ body: JSON.stringify({
+ title: modalData.title,
+ description: modalData.description,
+ status: modalData.status,
+ deadline: modalData.deadline || null,
+ })
+ })
+ .then(async (res) => {
+ const data = await res.json();
+ if (!res.ok) throw new Error(data?.errors?.[0]?.msg || "Failed to create task");
+ setTasks(prev => [data, ...prev]);
+ setShowModal(false);
+ resetForm();
+ })
+ .catch((e) => console.error(e));
+ }
+ }
+
+ function toggleComplete(id) {
+ const token = localStorage.getItem("token");
+ const task = tasks.find(t => t._id === id || t.id === id);
+ if (!task) return;
+ const newStatus = task.status === "pending" ? "completed" : "pending";
+ const taskId = task._id || task.id;
+ fetch(`${API}/${taskId}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json", "x-auth-token": token },
+ body: JSON.stringify({ status: newStatus })
+ })
+ .then(async (res) => {
+ const data = await res.json();
+ if (!res.ok) throw new Error(data?.errors?.[0]?.msg || "Failed to update task");
+ setTasks(prev => prev.map(t => (t._id || t.id) === taskId ? data : t));
+ })
+ .catch((e) => console.error(e));
+ }
+
+ function removeTask(id) {
+ const token = localStorage.getItem("token");
+ const task = tasks.find(t => t._id === id || t.id === id);
+ if (!task) return;
+ const taskId = task._id || task.id;
+ fetch(`${API}/${taskId}`, { method: "DELETE", headers: { "x-auth-token": token } })
+ .then(async (res) => {
+ if (!res.ok) {
+ const data = await res.json();
+ throw new Error(data?.errors?.[0]?.msg || "Failed to delete task");
+ }
+ setTasks(prev => prev.filter(t => (t._id || t.id) !== taskId));
+ })
+ .catch((e) => console.error(e));
+ }
+
+ function startEdit(task) {
+ setEditingId(task._id || task.id);
+ setModalData({
+ title: task.title,
+ description: task.description,
+ status: task.status,
+ deadline: task.deadline ? task.deadline.slice(0,16) : "",
+ });
+ setShowModal(true);
+ }
+
+ function GoalProgress() {
+ // simplistic mock: completed / total
+ const total = tasks.length || 1;
+ const done = completedTasks.length;
+ const percent = Math.round((done / total) * 100);
+ return (
+
+
+
Goals
+ {done}/{total} completed
+
+
+
Track milestones like "Solve X LeetCode" or "Finish project".
+
+ );
+ }
+
+ const TaskCard = ({ task }) => (
+
+
+
+
+
+
{task.title}
+ {task.description &&
{task.description}
}
+ {task.deadline && (
+
Due {new Date(task.deadline).toLocaleString()}
+ )}
+
+
+
+
+
+
+
+
+ );
+
+ function WeeklyGoals() {
+ const weeklyTarget = 7; // mock
+ const weeklyDone = Math.min(weeklyTarget, completedTasks.length);
+ const percent = Math.round((weeklyDone / weeklyTarget) * 100);
+ return (
+
+
+
Weekly Goals
+ {weeklyDone}/{weeklyTarget} this week
+
+
+
Set targets like "Solve 7 problems" weekly.
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
To-Do List
+
+
+
+
+
+
+
+
+
+ {/* Add / Edit Modal */}
+ {showModal && (
+
+
+
+
{editingId ? "Edit Task" : "Add Task"}
+
+
+
+
+
+ )}
+
+ {/* Content */}
+
+
+ {/* Pending */}
+
+
Pending
+
+ {filtered(pendingTasks).length === 0 ? (
+
No pending tasks.
+ ) : (
+ filtered(pendingTasks).map(task =>
)
+ )}
+
+
+
+ {/* Completed */}
+
+
Completed
+
+ {filtered(completedTasks).length === 0 ? (
+
No completed tasks yet.
+ ) : (
+ filtered(completedTasks).map(task =>
)
+ )}
+
+
+
+
+ {/* Right column widgets */}
+
+ {/* Move Quick Tips to top for visibility */}
+
+
Quick Tips
+
+ - Use deadlines to prioritize tasks.
+ - Break big goals into smaller, actionable items.
+ - Mark tasks done to track your streaks.
+
+
+
+
+
+
+
+
+
+ );
+}
+
+
diff --git a/frontend/src/Components/ui/backbutton.jsx b/frontend/src/Components/ui/backbutton.jsx
index dd5acd4..0f323cf 100644
--- a/frontend/src/Components/ui/backbutton.jsx
+++ b/frontend/src/Components/ui/backbutton.jsx
@@ -2,16 +2,21 @@ import React from "react";
import { ArrowLeft } from "lucide-react";
import { useNavigate } from "react-router-dom";
-export default function BackButton() {
+export default function BackButton({ to = null }) {
const navigate = useNavigate();
+ const handleClick = () => {
+ if (to) navigate(to);
+ else navigate(-1);
+ };
+
return (
);
}