Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* @tailwind base;
@tailwind components;
@tailwind utilities; */

@import "tailwindcss";
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,25 @@
"preview": "vite preview"
},
"dependencies": {
"date-fns": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"zustand": "^5.0.5"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@tailwindcss/postcss": "^4.1.7",
"@tailwindcss/vite": "^4.1.7",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"postcss": "^8.5.3",
"tailwindcss": "^4.1.7",
"vite": "^6.2.0"
}
}
43 changes: 40 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
export const App = () => {
import { useState } from "react";
import useTaskStore from "./data/useTaskData";
import TaskForm from "./Components/TaskForm";
import TaskList from "./Components/TaskList";
import TaskStats from "./Components/TaskStats";

export default function App() {
const { tasks, addTask, removeTask, toggleTask, completeAllTasks } =
useTaskStore();
const [input, setInput] = useState("");
const [dueDate, setDueDate] = useState(""); // date state

const handleSubmit = (e) => {
e.preventDefault();
if (!input.trim()) return;
addTask(input.trim(), dueDate); // pass dueDate to addTask
setInput("");
};

return (
<h1>React Boilerplate</h1>
)
<main className="min-h-screen bg-gray-100 p-4 flex flex-col items-center">
<h1 className="text-2xl font-bold mb-4">Task Manager</h1>
<div className="w-full max-w-md">
<TaskForm
onSubmit={handleSubmit}
input={input}
setInput={setInput}
dueDate={dueDate}
setDueDate={setDueDate}
/>
<TaskList tasks={tasks} onToggle={toggleTask} onRemove={removeTask} onCompleteAll={completeAllTasks} />
<TaskStats tasks={tasks} />
<button
onClick={completeAllTasks}
className="mb-4 bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Complete All
</button>
</div>
</main>
);
}
24 changes: 24 additions & 0 deletions src/Components/TaskForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default function TaskForm({ onSubmit, input, setInput,dueDate, setDueDate }) {
return (
<form onSubmit={onSubmit} className="flex gap-2 mb-4">
<input
type="text"
className="border rounded px-3 py-2"
placeholder="Add a task"
value={input}
onChange={(e) => setInput(e.target.value)}
aria-label="New task"
/>
<input
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
aria-label="Due date"
className="border rounded px-3 py-2"
/>
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded">
Add
</button>
</form>
);
}
35 changes: 35 additions & 0 deletions src/Components/TaskItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { format } from 'date-fns';


export default function TaskItem({ task, onToggle, onRemove }) {
return (
<li className="flex justify-between items-center bg-white px-4 py-2 rounded shadow">
<span
className={`flex-1 ${task.completed ? 'line-through text-gray-400' : ''}`}
role="button"
tabIndex={0}
onClick={() => onToggle(task.id)}
onKeyDown={(e) => e.key === 'Enter' && onToggle(task.id)}
>
{task.text}{task.dueDate && (
<span className="text-gray-500 text-sm ml-2">
{format(new Date(task.dueDate), 'dd/MM/yyyy')}
</span>
)}
{task.createdAt && (
<span className="block text-gray-400 text-xs mt-1">
Added: {format(new Date(task.createdAt), 'dd MMM yyyy, p')}
</span>
)}

</span>
<button
onClick={() => onRemove(task.id)}
className="text-red-600 ml-4"
aria-label={`Delete ${task.text}`}
>
</button>
</li>
);
}
41 changes: 41 additions & 0 deletions src/Components/TaskList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import TaskItem from './TaskItem';

export default function TaskList({ tasks, onToggle, onRemove, onCompleteAll }) {
if (tasks.length === 0) {
return (
<div className="text-center text-gray-500">No tasks yet. Start by adding one!</div>
);
}

const allCompleted = tasks.every(task => task.completed);


return (
<div>
<div className="flex justify-end mb-2">
<button
onClick={onCompleteAll}
disabled={allCompleted}
className={`px-4 py-2 rounded text-sm font-medium transition ${
allCompleted
? 'bg-gray-300 text-gray-600 cursor-not-allowed'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
Complete all
</button>
</div>

<ul className="space-y-2">
{tasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onToggle={onToggle}
onRemove={onRemove}
/>
))}
</ul>
</div>
);
}
10 changes: 10 additions & 0 deletions src/Components/TaskStats.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function TaskStats({ tasks }) {
const total = tasks.length;
const uncompleted = tasks.filter((t) => !t.completed).length;

return (
<div className="mt-4 text-sm text-gray-600">
Total: {total} | Uncompleted: {uncompleted}
</div>
);
}
34 changes: 34 additions & 0 deletions src/data/useTaskData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from "zustand";

const useTaskStore = create((set) => ({
tasks: [],
addTask: (text, dueDate) =>
set((state) => ({
tasks: [
...state.tasks,
{
id: Date.now(),
text,
dueDate,
completed: false,
createdAt: new Date().toISOString()
},
],
})),
removeTask: (id) =>
set((state) => ({
tasks: state.tasks.filter((task) => task.id !== id),
})),
toggleTask: (id) =>
set((state) => ({
tasks: state.tasks.map((task) =>
task.id === id ? { ...task, completed: !task.completed } : task
),
})),
completeAllTasks: () =>
set((state) => ({
tasks: state.tasks.map((task) => ({ ...task, completed: true })),
})),
}));

export default useTaskStore;
1 change: 1 addition & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}
@import "tailwindcss";
2 changes: 1 addition & 1 deletion src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom/client'

import { App } from './App.jsx'
import App from './App.jsx'

import './index.css'

Expand Down
4 changes: 2 additions & 2 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
plugins: [react(),tailwindcss()]
})