├── 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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------