├── src
├── vite-env.d.ts
├── assets
│ └── ken-cheung-KonWFWUaAuk-unsplash.webp
├── App.tsx
├── App.css
├── pages
│ ├── 404
│ │ ├── ErrorPage.css
│ │ └── ErrorPage.tsx
│ ├── pwa
│ │ └── Pwainstall.tsx
│ └── todo
│ │ ├── Todo.tsx
│ │ └── Todo.css
├── main.tsx
└── Routes
│ └── Routes.tsx
├── public
├── favicon.ico
├── todopre.png
├── TodoIcon2.png
├── todoico5.png
├── apple-touch-icon.png
└── ken-cheung-KonWFWUaAuk-unsplash.webp
├── tsconfig.node.json
├── .gitignore
├── .eslintrc.cjs
├── tsconfig.json
├── package.json
├── vite.config.ts
├── index.html
└── README.md
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/todopre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/todopre.png
--------------------------------------------------------------------------------
/public/TodoIcon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/TodoIcon2.png
--------------------------------------------------------------------------------
/public/todoico5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/todoico5.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/ken-cheung-KonWFWUaAuk-unsplash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/public/ken-cheung-KonWFWUaAuk-unsplash.webp
--------------------------------------------------------------------------------
/src/assets/ken-cheung-KonWFWUaAuk-unsplash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishakh-abhayan/Todo/HEAD/src/assets/ken-cheung-KonWFWUaAuk-unsplash.webp
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import Routes from "./Routes/Routes";
3 |
4 | function App(): JSX.Element {
5 | return ;
6 | }
7 |
8 | export default App;
9 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Shadows+Into+Light&family=Ysabeau+Infant:ital,wght@1,300&display=swap");
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
6 | -webkit-tap-highlight-color: transparent;
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .vercel
26 |
--------------------------------------------------------------------------------
/src/pages/404/ErrorPage.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Cabin+Sketch");
2 |
3 | .site h1 {
4 | font-family: "Cabin Sketch", cursive;
5 | font-size: 3em;
6 | text-align: center;
7 | opacity: 0.8;
8 | order: 1;
9 | }
10 |
11 | .site {
12 | height: 100vh;
13 | width: 100%;
14 | display: flex;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.tsx";
4 | import { Analytics } from "@vercel/analytics/react";
5 |
6 | import "react-toastify/dist/ReactToastify.css";
7 |
8 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/src/pages/404/ErrorPage.tsx:
--------------------------------------------------------------------------------
1 | import "./ErrorPage.css";
2 |
3 | function ErrorPage() {
4 | return (
5 |
6 |
10 |
11 |
12 | 404:
13 | Page Not Found
14 |
15 |
16 | );
17 | }
18 |
19 | export default ErrorPage;
20 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true, es2020: true },
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:react-hooks/recommended',
7 | ],
8 | parser: '@typescript-eslint/parser',
9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
10 | plugins: ['react-refresh'],
11 | rules: {
12 | 'react-refresh/only-export-components': 'warn',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/src/Routes/Routes.tsx:
--------------------------------------------------------------------------------
1 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
2 |
3 | import TodoPage from "../pages/todo/Todo";
4 | import ErrorPage from "../pages/404/ErrorPage";
5 |
6 | function Routes() {
7 | const router = createBrowserRouter([
8 | {
9 | path: "/",
10 | element: ,
11 | errorElement: ,
12 | },
13 | {
14 | path: "*",
15 | element: ,
16 | },
17 | ]);
18 | return ;
19 | }
20 |
21 | export default Routes;
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src", "src/pages/404/.tsx"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todonow",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "tsc && vite build",
9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@vercel/analytics": "^1.1.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-icons": "^4.9.0",
17 | "react-router-dom": "^6.11.2",
18 | "react-toastify": "^9.1.3"
19 | },
20 | "devDependencies": {
21 | "@types/react": "^18.0.37",
22 | "@types/react-dom": "^18.0.11",
23 | "@typescript-eslint/eslint-plugin": "^5.59.0",
24 | "@typescript-eslint/parser": "^5.59.0",
25 | "@vitejs/plugin-react": "^4.0.0",
26 | "eslint": "^8.38.0",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.3.4",
29 | "typescript": "^5.0.2",
30 | "vite": "^4.3.9",
31 | "vite-plugin-pwa": "^0.16.5",
32 | "workbox-window": "^7.0.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/pwa/Pwainstall.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 |
3 | const InstallPWA: React.FC = () => {
4 | const [supportsPWA, setSupportsPWA] = useState(false);
5 | const [promptInstall, setPromptInstall] = useState(null);
6 |
7 | useEffect(() => {
8 | const handler = (e: any) => {
9 | e.preventDefault();
10 | console.log("we are being triggered :D");
11 | setSupportsPWA(true);
12 | setPromptInstall(e);
13 | };
14 |
15 | window.addEventListener("beforeinstallprompt", handler);
16 |
17 | return () => window.removeEventListener("beforeinstallprompt", handler);
18 | }, []);
19 |
20 | const onClick = (evt: React.MouseEvent) => {
21 | evt.preventDefault();
22 | if (!promptInstall) {
23 | return;
24 | }
25 | promptInstall.prompt();
26 | };
27 |
28 | if (!supportsPWA) {
29 | return null;
30 | }
31 |
32 | return (
33 |
42 | );
43 | };
44 |
45 | export default InstallPWA;
46 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import { VitePWA, VitePWAOptions } from "vite-plugin-pwa";
4 |
5 | const manifestForPlugin: Partial = {
6 | registerType: "prompt",
7 | includeAssets: [
8 | "favicon.ico",
9 | "TodoIcon2.png",
10 | "todoico5.png",
11 | "ken-cheung-KonWFWUaAuk-unsplash.webp",
12 | ],
13 | manifest: {
14 | name: "ToDo",
15 | short_name: "ToDo",
16 | description: "I am a simple Todo app",
17 | icons: [
18 | {
19 | src: "/TodoIcon2.png",
20 | sizes: "256x256",
21 | type: "image/png",
22 | purpose: "favicon",
23 | },
24 | {
25 | src: "/todoico5.png",
26 | sizes: "512x512",
27 | type: "image/png",
28 | purpose: "favicon",
29 | },
30 | {
31 | src: "/TodoIcon2.png",
32 | sizes: "256x256",
33 | type: "image/png",
34 | purpose: "apple touch icon",
35 | },
36 | {
37 | src: "/todoico5.png",
38 | sizes: "512x512",
39 | type: "image/png",
40 | purpose: "any maskable",
41 | },
42 | ],
43 | theme_color: "#171717",
44 | background_color: "#f0e7db",
45 | display: "standalone",
46 | scope: "/",
47 | start_url: "/",
48 | orientation: "portrait",
49 | },
50 | };
51 |
52 | // https://vitejs.dev/config/
53 | export default defineConfig({
54 | base: "./",
55 | plugins: [react(), VitePWA(manifestForPlugin)],
56 | });
57 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ToDo
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Todo✨
5 |
6 |
7 | 
8 |
9 |
10 |
11 | TodoNow is a simple todo list application built with React and React Router. It allows users to create, manage, and track their tasks.
12 |
13 | ## Features
14 |
15 | - User authentication: Users can create an account and log in to access their personal todo list.
16 | - Todo management: Users can add new todos, mark them as completed, and delete them.
17 | - Persistence: User data and todo list are stored in the browser's local storage, allowing for data retention between sessions.
18 |
19 | ## Technologies Used
20 |
21 | - React
22 | - React Router
23 | - TypeScript
24 | - CSS
25 |
26 | ## Getting Started
27 |
28 | ### Prerequisites
29 |
30 | - Node.js: Make sure you have Node.js installed on your machine.
31 |
32 | ### Installation
33 |
34 | 1. Clone the repository:
35 |
36 | ```shell
37 | git clone https://github.com/your-username/TodoNow.git
38 | ```
39 | 2. Navigate to the project directory:
40 |
41 | ```shell
42 | cd /TodoNow
43 | ```
44 |
45 | 3. Install the project dependencies:
46 |
47 | ```shell
48 | npm install
49 | ```
50 |
51 | 4. Start the development server:
52 |
53 | ```shell
54 | npm run dev
55 | ```
56 |
57 | ## Usage
58 |
59 | - Create an account or log in using your existing credentials.
60 | - Add new todos by entering the task in the input field and pressing the enter key or clicking the Add button.
61 | - Mark a todo as completed by clicking on it. Click again to mark it as incomplete.
62 | - Delete a todo by clicking the trash bin icon next to it.
63 | - Log out from the application using the logout button in the navigation bar.
64 |
65 | ## Contributing
66 |
67 | Contributions are welcome! If you find any issues or have suggestions for improvements, please feel free to open an issue or submit a pull request.😊
68 |
69 |
70 |
71 |
72 |
73 |

74 |
75 |
--------------------------------------------------------------------------------
/src/pages/todo/Todo.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { toast } from "react-toastify";
3 | import "./Todo.css";
4 | import { ToastContainer } from "react-toastify";
5 | import { IoTrashBin } from "react-icons/io5";
6 | import { FaPen } from "react-icons/fa";
7 |
8 | interface TodoItem {
9 | id: number;
10 | text: string;
11 | completed: boolean;
12 | }
13 |
14 | function Todo() {
15 | const [todo, setTodo] = useState("");
16 | const [todos, setTodos] = useState([]);
17 |
18 | useEffect(() => {
19 | // Load todos from local storage when the component mounts
20 | const storedTodos = localStorage.getItem("todos");
21 | if (storedTodos) {
22 | setTodos(JSON.parse(storedTodos));
23 | }
24 | }, []);
25 |
26 | useEffect(() => {
27 | // Save todos to local storage whenever the todos state changes
28 | localStorage.setItem("todos", JSON.stringify(todos));
29 | }, [todos]);
30 |
31 | const handleInput = (e: React.ChangeEvent) => {
32 | setTodo(e.target.value);
33 | };
34 |
35 | const handleAddTodo = () => {
36 | if (todo.trim() !== "") {
37 | const newTodo: TodoItem = {
38 | id: Date.now(),
39 | text: todo,
40 | completed: false,
41 | };
42 | setTodos([...todos, newTodo]);
43 | setTodo("");
44 | toast.success("Todo created successfully", {
45 | position: "bottom-center",
46 | autoClose: 3000,
47 | hideProgressBar: false,
48 | closeOnClick: true,
49 | pauseOnHover: true,
50 | draggable: true,
51 | progress: undefined,
52 | theme: "light",
53 | });
54 | }
55 | };
56 |
57 | const handleDelete = (id: number) => {
58 | const updatedTodos = todos.filter((todo) => todo.id !== id);
59 | setTodos(updatedTodos);
60 | toast.error("Todo deleted successfully", {
61 | position: "bottom-center",
62 | autoClose: 3000,
63 | hideProgressBar: false,
64 | closeOnClick: true,
65 | pauseOnHover: true,
66 | draggable: true,
67 | progress: undefined,
68 | theme: "light",
69 | });
70 | };
71 |
72 | const handleToggleComplete = (id: number) => {
73 | const updatedTodos = todos.map((todo) =>
74 | todo.id === id ? { ...todo, completed: !todo.completed } : todo
75 | );
76 | setTodos(updatedTodos);
77 | };
78 |
79 | const handleKeyPress = (e: React.KeyboardEvent) => {
80 | if (e.key === "Enter") {
81 | handleAddTodo();
82 | }
83 | };
84 |
85 | // const handleDeleteAll = () => {
86 | // // localStorage.clear();
87 | // setTodos([]);
88 | // };
89 |
90 | const characterLimit = window.innerWidth <= 550 ? 20 : undefined;
91 |
92 | return (
93 |
94 |
95 |
ToDo
96 |
97 |
98 |
107 |
116 |
117 |
118 |
119 | {todos.map((todo) => (
120 |
121 |
handleToggleComplete(todo.id)}
123 | className={todo.completed ? "todo_true" : "todo_false"}
124 | >
125 | {todo.text}
126 |
127 | handleDelete(todo.id)}
130 | size={20}
131 | />
132 |
133 | ))}
134 |
135 |
136 |
(Mark it's done by clicking on it)
137 | {/*
handleDeleteAll()} className="nav_act">
138 | Delete All
139 |
*/}
140 |
141 |
142 |
143 |
144 | );
145 | }
146 |
147 | export default Todo;
148 |
--------------------------------------------------------------------------------
/src/pages/todo/Todo.css:
--------------------------------------------------------------------------------
1 | /* Base styles for all screen sizes */
2 | .app_todo {
3 | height: 100vh;
4 |
5 | background-image: linear-gradient(
6 | to right top,
7 | #fff0ff,
8 | #f6f2ff,
9 | #ebf5ff,
10 | #e2f7ff,
11 | #ddf9ff
12 | );
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | overflow: hidden;
17 | }
18 |
19 | .todo_cover {
20 | height: 14rem;
21 | margin-top: 2rem;
22 | width: 100%;
23 | max-width: 80rem;
24 | background-color: black;
25 | background: linear-gradient(
26 | 270deg,
27 | rgba(0, 219, 222, 0.5) 0%,
28 | rgba(252, 0, 255, 0.6) 100%
29 | ),
30 | url("../../assets/ken-cheung-KonWFWUaAuk-unsplash.webp");
31 | background-position: center;
32 | background-size: cover;
33 | display: flex;
34 | align-items: center;
35 | border-radius: 10px;
36 | }
37 |
38 | .app_title {
39 | color: #e2e2e2;
40 | font-family: sans-serif;
41 | font-size: 40px;
42 | margin-left: 5%;
43 | margin-top: 30px;
44 | }
45 |
46 | .input_contain {
47 | display: flex;
48 | justify-content: center;
49 | }
50 |
51 | .do_input {
52 | width: 35rem;
53 | height: 4rem;
54 | border: 1px solid #69696924;
55 | border-radius: 5px;
56 | position: relative;
57 | top: -25px;
58 | box-shadow: 10px 10px 10px rgba(158, 158, 158, 0.2);
59 | font-size: 30px;
60 |
61 | font-family: "Ysabeau Infant", sans-serif;
62 | outline: none;
63 | padding-left: 1rem;
64 | color: rgb(144, 142, 142);
65 | }
66 |
67 | ::placeholder {
68 | color: rgb(111, 111, 111);
69 | opacity: 0.4; /* Firefox */
70 | }
71 |
72 | ::-ms-input-placeholder {
73 | /* Edge 12 -18 */
74 | color: rgba(125, 125, 125, 0.425);
75 | }
76 |
77 | .todo_check {
78 | background-color: #ffffff;
79 | position: relative;
80 | padding: 5px;
81 | top: -20px;
82 | left: -35px;
83 | margin-bottom: 10px;
84 | z-index: 5;
85 | border: none;
86 | cursor: pointer;
87 | }
88 |
89 | .todo_section {
90 | width: 90%;
91 | max-width: 35rem;
92 | max-height: 450px;
93 | border-radius: 10px;
94 | display: flex;
95 | overflow: hidden;
96 | flex-direction: column;
97 | align-items: center;
98 | box-shadow: 10px 10px 10px rgba(158, 158, 158, 0.2);
99 | }
100 |
101 | .card_contain {
102 | width: 100%;
103 | max-height: 400px;
104 | overflow-y: scroll;
105 | z-index: 1;
106 | border-top-right-radius: 10px;
107 | border-top-left-radius: 10px;
108 | box-shadow: 10px 10px 10px rgba(158, 158, 158, 0.2);
109 | }
110 |
111 | .card_contain::-webkit-scrollbar {
112 | display: none;
113 | }
114 |
115 | .todo_card {
116 | z-index: 4;
117 | height: 4rem;
118 | width: 100%;
119 | background-color: #f0f0f0;
120 | border: 1px solid rgb(174, 174, 174);
121 | border-top: hidden;
122 | border-left: hidden;
123 | border-right: hidden;
124 | font-weight: lighter;
125 | display: flex;
126 | align-items: flex-end;
127 | font-family: "Ysabeau Infant", sans-serif;
128 | font-size: 14px;
129 | color: rgb(144, 142, 142);
130 | overflow-x: hidden;
131 | justify-content: space-between;
132 | align-items: center;
133 | }
134 |
135 | .todo_card h1 {
136 | margin-left: 10px;
137 | cursor: pointer;
138 | }
139 |
140 | .todo_bin {
141 | margin-right: 15px;
142 | cursor: pointer;
143 | }
144 |
145 | .todo_nav {
146 | height: 4rem;
147 | width: 100%;
148 | max-width: 35rem;
149 | border-bottom-left-radius: 10px;
150 | border-bottom-right-radius: 10px;
151 | display: flex;
152 | justify-content: space-around;
153 | align-items: center;
154 | z-index: 1;
155 | background-color: #f0f0f0;
156 | font-size: 24px;
157 | font-family: monospace;
158 | display: flex;
159 | flex-direction: column;
160 | }
161 |
162 | .todo_true {
163 | text-decoration: line-through;
164 | }
165 |
166 | .todo_false {
167 | text-decoration: none;
168 | }
169 |
170 | .nav_act {
171 | margin-top: 7px;
172 | cursor: pointer;
173 | color: #d88181;
174 | padding: 2px;
175 | border-radius: 3px;
176 | }
177 |
178 | .nav_cop {
179 | color: rgb(181, 130, 78);
180 | font-family: "Courier New", Courier, monospace;
181 | display: flex;
182 | }
183 |
184 | /* Media Queries for Responsive Design */
185 | @media screen and (max-width: 768px) {
186 | .app_todo {
187 | display: flex;
188 | align-items: center;
189 | }
190 | .todo_cover {
191 | margin-top: 0;
192 | width: 100%;
193 | height: 150px;
194 | border-radius: 0;
195 | }
196 | .todo_section {
197 | box-shadow: 10px 10px 10px rgba(158, 158, 158, 0.2);
198 | }
199 | .do_input {
200 | margin-left: 30px;
201 | width: 20rem;
202 | }
203 | .todo_nav {
204 | box-shadow: black;
205 | }
206 | .nav_act {
207 | font-size: 20px;
208 | }
209 |
210 | .nav_cop {
211 | font-size: 16px;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------