├── src ├── index.css ├── vite-env.d.ts ├── App.css ├── definitions.ts ├── components │ ├── loading.tsx │ ├── context │ │ └── themeContext.tsx │ ├── list-item.tsx │ └── todo-form.tsx ├── main.tsx ├── assets │ └── react.svg └── App.tsx ├── public ├── icon.png └── vite.svg ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── README.md ├── index.html ├── .gitpod.yml ├── .eslintrc.cjs ├── tsconfig.json ├── package.json └── LICENSE /src/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikuzweelisa/react-todo-app/HEAD/public/icon.png -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | esbuild: { 8 | loader: "jsx" 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .header { 2 | font-size: 30px; 3 | } 4 | .h-full { 5 | height: 100vh; 6 | } 7 | 8 | .bg { 9 | background-color: #1E1E1E; /* Darker Gray for main background */ 10 | } 11 | 12 | .secondary-bg { 13 | background-color: #282828; /* Slate Gray for secondary containers */ 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /src/definitions.ts: -------------------------------------------------------------------------------- 1 | export type Todo={ 2 | id: number, 3 | text: string, 4 | completed: boolean, 5 | }; 6 | 7 | type TodoItem={ 8 | id: number, 9 | text: string, 10 | completed: boolean, 11 | toggle: () => void, 12 | onDelete: () => void, 13 | } 14 | type TodoFormProps={ 15 | addNote: (text: string) => void, 16 | 17 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | ## Getting Started 7 | 8 | First, run the development server: 9 | 10 | ```bash 11 | npm run dev 12 | # or 13 | yarn dev 14 | # or 15 | pnpm dev 16 | # or 17 | bun dev 18 | ``` 19 | -------------------------------------------------------------------------------- /src/components/loading.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Box, LinearProgress } from "@mui/material"; 3 | export default function Loading() { 4 | return
5 | 6 | logo 7 | 8 | 9 |
10 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Todo App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: npm install && npm run build 9 | command: npm run dev 10 | 11 | 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/components/context/themeContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from "react"; 2 | const ThemeContext = createContext(undefined); 3 | 4 | 5 | function ThemeProvider({ children }) { 6 | const [darkTheme, setDarkTheme] = useState(false); 7 | 8 | function toggleTheme() { 9 | setDarkTheme(prev => !prev); 10 | } 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | export { ThemeProvider, ThemeContext } -------------------------------------------------------------------------------- /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"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | import { Toaster } from "react-hot-toast"; 6 | import { ThemeProvider } from "./components/context/themeContext"; 7 | import { Suspense } from "react"; 8 | import Loading from "./components/loading"; 9 | // Ensure the root element is not null 10 | const rootElement = document.getElementById("root"); 11 | if (!rootElement) { 12 | throw new Error("Root element not found"); 13 | } 14 | 15 | const root = ReactDOM.createRoot(rootElement); 16 | root.render( 17 | 18 | 19 | 20 | }> 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todoapp", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.11.4", 13 | "@emotion/styled": "^11.11.5", 14 | "@mui/icons-material": "^5.15.19", 15 | "@mui/material": "^5.15.19", 16 | "@mui/styled-engine-sc": "^6.0.0-alpha.18", 17 | "bootstrap": "^5.3.3", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-hot-toast": "^2.4.1", 21 | "react-icons": "^5.2.0", 22 | "styled-components": "^6.1.11" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "^18.2.26", 26 | "@types/react-dom": "^18.2.10", 27 | "@vitejs/plugin-react": "^4.2.1", 28 | "eslint-plugin-react-refresh": "^0.4.6", 29 | "typescript": "^5.2.2", 30 | "vite": "^5.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Shema Elisa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/list-item.tsx: -------------------------------------------------------------------------------- 1 | import { FaTrashCan } from "react-icons/fa6"; 2 | import { Checkbox, Button } from "@mui/material"; 3 | import { useContext } from "react"; 4 | import { ThemeContext } from "./context/themeContext"; 5 | 6 | 7 | export default function ListItem({ 8 | title, 9 | completed, 10 | id, 11 | onDelete, 12 | toggle, 13 | }) { 14 | 15 | const themeContext = useContext(ThemeContext); 16 | const { darkTheme } = themeContext; 17 | 18 | return ( 19 |
  • 22 |
    23 | toggle(id)} checked={completed} /> 24 | 27 | {title} 28 | 29 | 37 |
    38 |
  • 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/todo-form.tsx: -------------------------------------------------------------------------------- 1 | import { FaNoteSticky, FaPlus } from "react-icons/fa6"; 2 | import { TextField, Button } from "@mui/material"; 3 | import { ThemeContext } from "./context/themeContext"; 4 | import { useContext } from "react"; 5 | 6 | 7 | 8 | export default function TodoForm({ submit, change, text }) { 9 | 10 | const themeContext = useContext(ThemeContext); 11 | const { darkTheme } = themeContext; 12 | return ( 13 |
    14 | 15 |
    16 | Add Todos 17 |
    18 |
    19 | 20 | 27 | 28 |
    29 | 37 |
    38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import "bootstrap/dist/css/bootstrap.min.css"; 3 | import TodoForm from "./components/todo-form"; 4 | import { useContext, useEffect, useState, ChangeEvent, FormEvent } from "react"; 5 | import ListItem from "./components/list-item"; 6 | import { FaNoteSticky, FaSun, FaTrashCan, FaMoon } from "react-icons/fa6"; 7 | import toast from "react-hot-toast"; 8 | import { Card, IconButton } from "@mui/material"; 9 | import { ThemeContext } from "./components/context/themeContext"; 10 | import Loading from "./components/loading"; 11 | 12 | 13 | export default function App() { 14 | const { toggleTheme, darkTheme } = useContext(ThemeContext); 15 | 16 | const [notes, setNotes] = useState(() => { 17 | const value = localStorage.getItem("notes"); 18 | return value ? JSON.parse(value) : []; 19 | }); 20 | useEffect(()=>{ 21 | window.localStorage.setItem("notes",JSON.stringify(notes)) 22 | },[notes]) 23 | const [text, setText] = useState(""); 24 | 25 | const [isLoading, setIsLoading] = useState(true); 26 | 27 | 28 | useEffect(() => { 29 | const timer = setTimeout(() => { 30 | setIsLoading(false); 31 | }, 5000); 32 | 33 | return () => clearTimeout(timer); 34 | }, []); 35 | 36 | if (isLoading) { 37 | return ; 38 | } 39 | 40 | function handleChange(e) { 41 | setText(e.target.value); 42 | } 43 | 44 | function addNote() { 45 | setNotes((prevNotes) => [ 46 | ...prevNotes, 47 | { id: crypto.randomUUID(), title: text, completed: false }, 48 | ]); 49 | } 50 | 51 | function handleSubmit(e) { 52 | e.preventDefault(); 53 | toast.promise( 54 | new Promise((resolve) => { 55 | addNote(); 56 | resolve(); 57 | }), 58 | { 59 | loading: "Adding note...", 60 | success: {`${text} Added!`}, 61 | error: Failed to add note, 62 | }, 63 | { 64 | duration: 8000, 65 | } 66 | ); 67 | setText(""); 68 | } 69 | 70 | function handleDelete(id) { 71 | setNotes((prevNotes) => prevNotes.filter((note) => note.id !== id)); 72 | toast.success("Todo Deleted!"); 73 | } 74 | 75 | function toggleNote(id) { 76 | setNotes((prevNotes) => 77 | prevNotes.map((note) => 78 | note.id === id ? { ...note, completed: !note.completed } : note 79 | ) 80 | ); 81 | } 82 | 83 | 84 | return ( 85 |
    89 |
    90 |
    91 | 92 | logo 93 | TODO APP 94 | 95 |
    96 | 97 | 104 | 105 |
    106 | 107 |
    108 | 112 | 113 | 114 | 118 | 122 | Todos 123 | {notes.length} 124 | 125 | {notes.length !== 0 && ( 126 |
    127 | 128 | Remove all{" "} 129 | setNotes([])}> 130 | 131 | {" "} 132 | 133 |
    134 | )} 135 |
      136 | {notes.length === 0 ? ( 137 |
    • 138 | No Todos available 139 |
    • 140 | ) : ( 141 | notes.map((note) => ( 142 | 150 | )) 151 | )} 152 |
    153 |
    154 |
    155 |
    156 | ) 157 | } 158 | --------------------------------------------------------------------------------