Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 12 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/src/App.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<title>Todo</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx">
</script>
<script type="module" src="./src/main.jsx"></script>
</body>
</html>
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/roboto": "^5.2.6",
"@mui/icons-material": "^7.1.2",
"@mui/material": "^7.1.2",
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/vite": "^4.1.11",
"postcss": "^8.5.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"tailwindcss": "^4.1.11",
"typeface-roboto": "^1.1.13",
"zustand": "^5.0.6"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/node": "^24.0.7",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
Expand Down
5 changes: 5 additions & 0 deletions postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
Binary file added public/plus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions public/trashcan-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions public/trashcan-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";

:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}

html,
body,
#root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: #18181b; /* Tailwind's bg-zinc-900 */
color: white;
}
13 changes: 9 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";

import { MainCard } from "./components/MainCard";

export const App = () => {
return (
<h1>React Boilerplate</h1>
)
}
return <MainCard />;
};
Empty file.
Empty file added src/components/CurrentTasks.jsx
Empty file.
37 changes: 37 additions & 0 deletions src/components/MainCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NewTaskIcon } from "./NewTaskIcon";
import { TaskCard } from "./TaskCard";
import { TaskFormWrapper } from "./TaskFormWrapper";
import { TaskContentStore } from "../stores/TaskContentStore";

export const MainCard = () => {
const tasks = TaskContentStore((state) => state.tasks);
const taskCount = tasks.length;

const today = new Date();

// Format: July 2, 2025
const formattedDate = today.toLocaleDateString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
});

// Format: Wednesday
const dayName = today.toLocaleDateString(undefined, { weekday: "long" });

return (
<main className="bg-zinc-900 text-white flex flex-col px-[20px] py-[40px] w-full">
<section className="w-full">
<p className="text-sm md:text-2xl text-zinc-400">{formattedDate}</p>
<h1 className="text-3xl md:text-7xl font-bold">{dayName}</h1>
<p className="text-sm md:text-2xl text-zinc-400">
Amount of tasks: {taskCount}
</p>
</section>
<div className="mt-6 w-full">
<TaskCard />
</div>
<TaskFormWrapper />
</main>
);
};
35 changes: 35 additions & 0 deletions src/components/NewTaskCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import { TaskContentStore } from "../stores/TaskContentStore";
import { UseUIStore } from "../stores/UseUIStore";
/*falsely flagged as error by vscode */

export const NewTaskCard = () => {
const [message, setMessage] = useState("");
const createTask = TaskContentStore((state) => state.createTask);
const closeForm = UseUIStore((state) => state.closeForm);

const handleSubmit = (e) => {
e.preventDefault();
if (!message.trim()) return; // prevent empty tasks
createTask(message);
setMessage("");
closeForm();
};

return (
<form onSubmit={handleSubmit} className="flex gap-4 flex-col md:w-full">
<textarea
id="tasks"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => setMessage(e.target.value)}
/*above is remain of trying to submit on enter key, help would be appreciated */
placeholder="Your task"
className="flex gap-2 w-full p-2 rounded border border-zinc-300 focus:outline-none"
/>
<button type="submit" className="hover:scale-105">
Send
</button>
</form>
);
};
10 changes: 10 additions & 0 deletions src/components/NewTaskIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const NewTaskIcon = ({ onClick }) => {
return (
<div
onClick={onClick}
className="fixed bottom-[40px] right-[40px] bg-white rounded-full shadow-lg hover:scale-105 transition-transform cursor-pointer w-[64px] h-[64px] flex items-center justify-center"
>
<img src="./plus.png" alt="Add task" className="w-8 h-8" />
</div>
);
};
21 changes: 21 additions & 0 deletions src/components/TaskCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TaskContentStore } from "../stores/TaskContentStore";
import { TaskList } from "./TaskList";

export const TaskCard = () => {
const tasks = TaskContentStore((state) => state.tasks);

return (
<>
<section className="space-y-2 md:space-y-4 w-full">
<h2 className="text-xl md:text-2xl font-semibold">Todo</h2>
</section>
<section className="max-w-full w-full space-y-2 md:space-y-4 md:mt-4 mt-2">
{tasks.length === 0 ? (
<p className="text-zinc-400 italic">you have nothing to do!</p>
) : (
<TaskList />
)}
</section>
</>
);
};
46 changes: 46 additions & 0 deletions src/components/TaskFormWrapper.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from "react";
import { NewTaskCard } from "./NewTaskCard";
import { NewTaskIcon } from "./NewTaskIcon";
import { UseUIStore } from "../stores/UseUIStore";

export const TaskFormWrapper = () => {
const formVisible = UseUIStore((state) => state.formVisible);
const toggleForm = UseUIStore((state) => state.toggleForm);
const closeForm = UseUIStore((state) => state.closeForm);

useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === "Escape") {
closeForm();
}
};

if (formVisible) {
window.addEventListener("keydown", handleKeyDown);
}

return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [formVisible, closeForm]);

return (
<div className="fixed bottom-4 right-4 z-50">
<NewTaskIcon onClick={toggleForm} />

{/* Form Wrapper */}
<div
className={`md:w-4/6 transform transition-all duration-300 ease-in-out
fixed bottom-[40px] right-4 w-64 rounded-xl shadow-xl
bg-zinc-700 p-4
${
formVisible
? "translate-y-0 opacity-100 pointer-events-auto"
: "translate-y-10 opacity-0 pointer-events-none"
}`}
>
<NewTaskCard />
</div>
</div>
);
};
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 { TaskContentStore } from "../stores/TaskContentStore";

export const TaskList = () => {
const tasks = TaskContentStore((state) => state.tasks);
const deleteTask = TaskContentStore((state) => state.deleteTask);

return (
<>
{tasks.map((task) => (
<div
key={task.id}
className="flex items-center gap-3 p-3 md:gap-10 bg-zinc-800 rounded-lg shadow"
>
{/* Checkbox */}
<input
type="checkbox"
id={`task-${task.id}`}
className="peer size-[20px]"
/>

{/* Label acts as a checkbox visually */}
<label
htmlFor={`task-${task.id}`}
className="flex flex-col cursor-pointer text-white peer-checked:line-through peer-checked:text-zinc-500 md:text-xl"
>
<span>{task.message}</span>
<span className="text-xs md:text-md text-zinc-400">
{task.date}
</span>
</label>
<button
onClick={() => deleteTask(task.id)}
className="text-red-400 hover:text-red-300 text-sm md:text-xl"
>
</button>
</div>
))}
</>
);
};
3 changes: 0 additions & 3 deletions src/index.css

This file was deleted.

12 changes: 6 additions & 6 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import React from "react";
import ReactDOM from "react-dom/client";

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

import './index.css'
import "./App.css";

ReactDOM.createRoot(document.getElementById('root')).render(
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
);
43 changes: 43 additions & 0 deletions src/stores/TaskContentStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";

const InitialState = {
tasks: [
{
id: Date.now(),
message: "I need to do something!",
date: new Date().toLocaleString(),
},
],
};

//object
export const TaskContentStore = create(
persist(
(set) => ({
...InitialState,

createTask: (message) => {
const newTask = {
id: Date.now(),
message,
date: new Date().toLocaleString(),
};
set((state) => ({
tasks: [...state.tasks, newTask],
}));
},

deleteTask: (id) => {
set((state) => ({
tasks: state.tasks.filter((task) => task.id !== id),
}));
},
}),
{
name: "task-storage", // localStorage key
}
)
);

/*Vite despises the jsx extension together with Zustand */
7 changes: 7 additions & 0 deletions src/stores/UseUIStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { create } from "zustand";

export const UseUIStore = create((set) => ({
formVisible: false,
toggleForm: () => set((state) => ({ formVisible: !state.formVisible })),
closeForm: () => set({ formVisible: false }),
}));
4 changes: 4 additions & 0 deletions todo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
full site view - white and black
implement local storage
all - completed - uncompleted
date and date-stamp on card
Loading