├── 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 |
7 |
8 |
9 |
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 | ![Vite + React + TS (1)](https://github.com/vishakh-abhayan/TodoNow/assets/94307781/b69a1d7e-04f2-42b8-b2c0-1730c8208f6e) 8 | 9 | Todo - Your Personal Task Manager for a Productive Day | Product Hunt 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 | --------------------------------------------------------------------------------