├── public ├── logo.png ├── robots.txt ├── project-preview.png ├── manifest.json └── index.html ├── src ├── redux │ ├── store.js │ └── todoSlice.js ├── components │ ├── TodoList.js │ ├── TotalCompleteItems.js │ ├── ToggleMode.js │ ├── AddTodoForm.js │ └── TodoItem.js ├── index.js ├── App.js └── assets │ └── index.css ├── .gitignore ├── LICENSE.md ├── package.json └── README.md /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catherineisonline/what-todo/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/project-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catherineisonline/what-todo/HEAD/public/project-preview.png -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import todoReducer from "./todoSlice"; 3 | 4 | export default configureStore({ 5 | reducer: { 6 | todos: todoReducer, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo.png", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "logo.png", 16 | "type": "image/png" 17 | } 18 | ], 19 | "start_url": ".", 20 | "display": "standalone", 21 | "theme_color": "#000000", 22 | "background_color": "#ffffff" 23 | } -------------------------------------------------------------------------------- /src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //Redux 3 | import TodoItem from "./TodoItem"; 4 | import { useSelector } from "react-redux"; 5 | 6 | const TodoList = () => { 7 | const todos = useSelector((state) => state.todos); 8 | return ( 9 | 19 | ); 20 | }; 21 | 22 | export default TodoList; 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | What Todo 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/TotalCompleteItems.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //Redux 3 | import { useSelector } from "react-redux"; 4 | 5 | const TotalCompleteItems = () => { 6 | const completed = useSelector((state) => 7 | state.todos.filter((todo) => todo.completed === true) 8 | ); 9 | const uncompleted = useSelector((state) => 10 | state.todos.filter((todo) => todo.completed === false) 11 | ); 12 | return ( 13 |
14 |

Tasks: {uncompleted.length}

15 |

Completed: {completed.length}

16 |
17 | ); 18 | }; 19 | 20 | export default TotalCompleteItems; 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 4 | //Styles 5 | import "../src/assets/index.css"; 6 | import App from "./App"; 7 | //Redux 8 | import store from "./redux/store"; 9 | import { Provider } from "react-redux"; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | } /> 17 | 18 | 19 | 20 | , 21 | document.getElementById("root") 22 | ); 23 | -------------------------------------------------------------------------------- /src/components/ToggleMode.js: -------------------------------------------------------------------------------- 1 | import { FaMoon, FaSun } from "react-icons/fa"; 2 | import { useState } from "react"; 3 | 4 | export default function ToggleMode() { 5 | const [darkMode, setDarkMode] = useState(false); 6 | const changeTheme = () => { 7 | document.body.classList.toggle("dark"); 8 | setDarkMode(!darkMode); 9 | }; 10 | return ( 11 |
12 | {darkMode ? ( 13 |
14 | 15 |
16 | ) : ( 17 |
18 | 19 |
20 | )} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddTodoForm from "./components/AddTodoForm"; 3 | import TodoList from "./components/TodoList"; 4 | import TotalCompleteItems from "./components/TotalCompleteItems"; 5 | import ToggleMode from "./components/ToggleMode"; 6 | 7 | const App = () => { 8 | return ( 9 | <> 10 |
11 |
12 |

What Todo

13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 25 | 26 | ); 27 | }; 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /src/components/AddTodoForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | //Redux 3 | import { useDispatch } from "react-redux"; 4 | import { addTodo } from "../redux/todoSlice"; 5 | 6 | const AddTodoForm = () => { 7 | const [value, setValue] = useState(""); 8 | const dispatch = useDispatch(); 9 | const onSubmit = (event) => { 10 | event.preventDefault(); 11 | if (value) { 12 | dispatch(addTodo({ title: value })); 13 | } 14 | }; 15 | return ( 16 |
17 | setValue(event.target.value)} 23 | /> 24 | 27 |
28 | ); 29 | }; 30 | 31 | export default AddTodoForm; 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ekaterine (Catherine) Mitagvaria 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. -------------------------------------------------------------------------------- /src/redux/todoSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | //Random ID 3 | import { v4 as uuid } from "uuid"; 4 | 5 | export const todoSlice = createSlice({ 6 | name: "todos", 7 | initialState: [ 8 | { id: uuid(), title: "Make a to do list", completed: false }, 9 | { id: uuid(), title: "Check off the first item", completed: false }, 10 | { 11 | id: uuid(), 12 | title: "Realize you already did two things on the list", 13 | completed: false, 14 | }, 15 | { 16 | id: uuid(), 17 | title: "Reward yourself with a nice cup of coffee", 18 | completed: false, 19 | }, 20 | ], 21 | reducers: { 22 | addTodo: (state, action) => { 23 | const todo = { 24 | id: uuid(), 25 | title: action.payload.title, 26 | completed: false, 27 | }; 28 | state.push(todo); 29 | }, 30 | markComplete: (state, action) => { 31 | const index = state.findIndex((todo) => todo.id === action.payload.id); 32 | state[index].completed = action.payload.completed; 33 | }, 34 | deleteItem: (state, action) => { 35 | return state.filter((todo) => todo.id !== action.payload.id); 36 | }, 37 | }, 38 | }); 39 | 40 | export const { addTodo, markComplete, deleteItem } = todoSlice.actions; 41 | 42 | export default todoSlice.reducer; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-app", 3 | "version": "0.1.0", 4 | "homepage": "https://catherineisonline.github.io/what-todo", 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.5.0", 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "bootstrap": "^4.6.0", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-icons": "^4.3.1", 14 | "react-redux": "^7.2.2", 15 | "react-router-dom": "^6.3.0", 16 | "react-scripts": "^5.0.1", 17 | "redux-logger": "^3.0.6", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject", 25 | "predeploy": "npm run build", 26 | "deploy": "gh-pages -d build" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "gh-pages": "^6.1.1" 48 | } 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # What Todo - A simple to do list 4 | 5 | What Todo is a simple to-do list application that was created as a practice project to introduce the use of Redux for the first time. 6 | The project is built using React and CSS. The application allows users to create and manage a list of tasks, and can include features such as adding new tasks, editing existing tasks, marking tasks as completed, and deleting tasks. Additionally, the project is built with Redux, a JavaScript library for managing application state, making it easy to manage the state of the to-do list, and providing a predictable and consistent way of handling updates and changes to the data. The goal of this project is to provide a simple and easy to use to-do list application while practicing the use of Redux and its concepts such as actions, reducers, and store. 7 | 8 | 9 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 10 | 11 | ### Available Scripts 12 | 13 | In the project directory, you can run: 14 | 15 | ##### `npm start` 16 | 17 | Runs the app in the development mode.\ 18 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 19 | 20 | The page will reload when you make changes.\ 21 | You may also see any lint errors in the console. 22 | 23 | ##### `npm test` 24 | 25 | Launches the test runner in the interactive watch mode.\ 26 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 27 | 28 | ##### `npm run build` 29 | 30 | Builds the app for production to the `build` folder.\ 31 | It correctly bundles React in production mode and optimizes the build for the best performance. 32 | 33 | The build is minified and the filenames include the hashes.\ 34 | Your app is ready to be deployed! 35 | 36 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 37 | -------------------------------------------------------------------------------- /src/components/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState } from "react"; 3 | //Redux 4 | import { useDispatch } from "react-redux"; 5 | import { markComplete, deleteItem } from "../redux/todoSlice"; 6 | const TodoItem = ({ id, title, completed }) => { 7 | const dispatch = useDispatch(); 8 | const [isActive, setActive] = useState(false); 9 | const markCompleteds = () => { 10 | dispatch(markComplete({ id, completed: !completed })); 11 | setActive(!isActive); 12 | }; 13 | const deleteAction = () => { 14 | dispatch(deleteItem({ id })); 15 | }; 16 | return ( 17 | <> 18 | {isActive ? ( 19 |
  • 20 |
    21 | 30 |

    {title}

    31 |
    32 |
    33 | 41 |
    42 |
  • 43 | ) : ( 44 |
  • 45 |
    46 | 55 |

    {title}

    56 |
    57 |
    58 | 66 |
    67 |
  • 68 | )} 69 | 70 | ); 71 | }; 72 | 73 | export default TodoItem; 74 | -------------------------------------------------------------------------------- /src/assets/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=DM+Serif+Display&display=swap"); 2 | :root { 3 | --title-font: "DM Serif Display", serif; 4 | --body-bg: #e3dafc; 5 | --white: #ffffff; 6 | --green: #5cc6bf; 7 | --red: #ff0000; 8 | --glass-gray: #ffffff26; 9 | --bright-purple: #6c46ff; 10 | --dark-purple: #402a9c; 11 | --light-purple: #9a7ef5; 12 | --rose-purple: #c9b7f6; 13 | --box-shadow: 1px 8px 26px -8px rgba(0, 0, 0, 0.7); 14 | --purple-gradient: linear-gradient(6deg, rgba(108, 70, 255, 1) 31%, rgb(102, 69, 211) 67%, rgb(108, 85, 168) 100%); 15 | --light-purple-gradient: linear-gradient( 16 | 6deg, 17 | rgba(108, 70, 255, 1) 31%, 18 | rgba(163, 137, 250, 1) 67%, 19 | rgba(201, 183, 246, 1) 100% 20 | ); 21 | --two-color-gradient: linear-gradient(6deg, rgba(108, 70, 255, 1) 7%, rgba(201, 183, 246, 1) 100%); 22 | } 23 | /* GENERAL STYLING */ 24 | * { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | body { 30 | -ms-overflow-style: none; /* for Internet Explorer, Edge */ 31 | scrollbar-width: none; /* for Firefox */ 32 | overflow-y: scroll; 33 | box-sizing: border-box; 34 | display: flex; 35 | flex-direction: column; 36 | min-height: 100vh; 37 | background-color: var(--body-bg); 38 | } 39 | body::-webkit-scrollbar { 40 | display: none; /* for Chrome, Safari, and Opera */ 41 | } 42 | 43 | image { 44 | display: block; 45 | } 46 | 47 | ul, 48 | ol, 49 | li { 50 | padding: 0; 51 | margin: 0; 52 | list-style: none; 53 | } 54 | 55 | /* APP */ 56 | main { 57 | width: 90%; 58 | margin: 0 auto; 59 | display: flex; 60 | flex-direction: column; 61 | } 62 | .title-section { 63 | padding: 2rem; 64 | display: flex; 65 | flex-direction: row; 66 | align-items: center; 67 | justify-content: space-between; 68 | font-family: var(--title-font); 69 | text-transform: uppercase; 70 | font-size: 1.5rem; 71 | letter-spacing: 1px; 72 | } 73 | /* TOGGLE MODE */ 74 | .toggle-light, 75 | .toggle-dark { 76 | font-size: 1.7rem; 77 | cursor: pointer; 78 | } 79 | .dark { 80 | background-color: var(--dark-purple); 81 | color: var(--light-purple); 82 | } 83 | .dark .todo-section { 84 | background: var(--bright-purple); 85 | background: var(--purple-gradient); 86 | } 87 | .dark .toggle-light { 88 | color: var(--light-purple); 89 | } 90 | 91 | /* TODO SECTION */ 92 | .todo-section { 93 | display: inline-flex; 94 | flex-direction: column; 95 | gap: 1rem; 96 | background: var(--bright-purple); 97 | background: var(--light-purple-gradient); 98 | border-radius: 25px; 99 | padding: 1.2rem; 100 | margin: 0 auto; 101 | width: auto; 102 | max-width: 22rem; 103 | -webkit-box-shadow: var(--box-shadow); 104 | -moz-box-shadow: var(--box-shadow); 105 | box-shadow: var(--box-shadow); 106 | } 107 | 108 | /* FORM */ 109 | .form-input { 110 | display: flex; 111 | flex-direction: row; 112 | justify-content: space-between; 113 | -webkit-box-shadow: var(--box-shadow); 114 | -moz-box-shadow: var(--box-shadow); 115 | box-shadow: var(--box-shadow); 116 | border-radius: 10px; 117 | } 118 | 119 | .form-input input { 120 | background-color: var(--glass-gray); 121 | backdrop-filter: blur(5px); 122 | border: 1px solid transparent; 123 | outline: none; 124 | border-radius: 10px 0 0 10px; 125 | padding: 1rem; 126 | width: 100%; 127 | } 128 | .form-input input::placeholder, 129 | .form-input input[type="text"] { 130 | color: var(--white); 131 | font-family: var(--body-font); 132 | font-weight: 400; 133 | font-size: 1.2rem; 134 | padding: 5px; 135 | } 136 | 137 | .form-input button { 138 | cursor: pointer; 139 | border: none; 140 | border-radius: 0 10px 10px 0; 141 | background: var(--bright-purple); 142 | background: var(--two-color-gradient); 143 | padding: 1.3rem; 144 | color: var(--rose-purple); 145 | font-size: 1.2rem; 146 | } 147 | 148 | /* TodoList */ 149 | .todo-list { 150 | display: flex; 151 | flex-direction: column; 152 | gap: 2rem; 153 | } 154 | /* TodoItem */ 155 | .todo-item { 156 | display: flex; 157 | flex-direction: row; 158 | justify-content: space-between; 159 | align-items: center; 160 | color: var(--white); 161 | background-color: var(--glass-gray); 162 | backdrop-filter: blur(5px); 163 | -webkit-box-shadow: var(--box-shadow); 164 | -moz-box-shadow: var(--box-shadow); 165 | box-shadow: var(--box-shadow); 166 | border-radius: 10px; 167 | padding: 1rem; 168 | font-family: var(--body-font); 169 | font-size: 1.2rem; 170 | } 171 | .todo-item.completed { 172 | background-color: var(--green); 173 | } 174 | 175 | .todo-item section { 176 | display: flex; 177 | flex-direction: row; 178 | align-items: center; 179 | } 180 | /* CUSTOM CHECKBOX */ 181 | .item { 182 | display: flex; 183 | flex-direction: row; 184 | align-items: center; 185 | gap: 1rem; 186 | } 187 | .item-name { 188 | display: flex; 189 | flex-direction: row; 190 | align-items: center; 191 | -webkit-user-select: none; 192 | -moz-user-select: none; 193 | -ms-user-select: none; 194 | user-select: none; 195 | } 196 | 197 | /* Hide the browser's default radio button */ 198 | .item-name input { 199 | display: none; 200 | } 201 | 202 | /* Create a custom radio button */ 203 | .checkmark { 204 | height: 20px; 205 | width: 20px; 206 | background-color: var(--glass-gray); 207 | backdrop-filter: blur(5px); 208 | border-radius: 50%; 209 | cursor: pointer; 210 | } 211 | 212 | /* When the radio button is checked, add a blue background */ 213 | .item-name input:checked ~ .checkmark { 214 | background-color: var(--white); 215 | } 216 | 217 | /* Create the checkmark/indicator (hidden when not checked) */ 218 | .checkmark:after { 219 | content: ""; 220 | position: absolute; 221 | display: none; 222 | } 223 | 224 | /* Show the checkmark when checked */ 225 | .container input:checked ~ .checkmark:after { 226 | display: block; 227 | } 228 | 229 | /* Style the checkmark/indicator */ 230 | .container .checkmark:after { 231 | left: 9px; 232 | top: 5px; 233 | width: 5px; 234 | height: 10px; 235 | border: solid var(--white); 236 | border-width: 0 3px 3px 0; 237 | -webkit-transform: rotate(45deg); 238 | -ms-transform: rotate(45deg); 239 | transform: rotate(45deg); 240 | } 241 | 242 | /* TotalCompleteItems */ 243 | .total-complete-items { 244 | display: flex; 245 | flex-direction: row; 246 | gap: 2rem; 247 | align-items: center; 248 | height: 5rem; 249 | max-width: 22rem; 250 | margin: auto; 251 | } 252 | /* DELETE BUTTON */ 253 | .todo-item { 254 | font-size: 1.1rem; 255 | } 256 | .delete-btn { 257 | color: var(--bright-purple); 258 | background-color: transparent; 259 | border: none; 260 | cursor: pointer; 261 | font-size: 1.3rem; 262 | padding: 5px; 263 | } 264 | .todo-item.completed .delete-btn { 265 | color: var(--red); 266 | padding: 5px; 267 | } 268 | .todo-item.completed p { 269 | text-decoration: line-through; 270 | } 271 | /* TRANSITIONS */ 272 | .smooth-transition, 273 | .checkmark, 274 | .delete-btn, 275 | .todo-item.completed .delete-btn { 276 | transition: all ease-in-out 0.3s; 277 | } 278 | 279 | @media (hover: hover) { 280 | .form-input button:hover { 281 | opacity: 0.6; 282 | transition: all ease-in-out 0.3s; 283 | } 284 | .checkmark:hover { 285 | background-color: var(--bright-purple); 286 | transition: all ease-in-out 0.3s; 287 | } 288 | .delete-btn:hover, 289 | .todo-item.completed .delete-btn:hover { 290 | color: var(--white); 291 | transition: all ease-in-out 0.3s; 292 | } 293 | } 294 | --------------------------------------------------------------------------------