diff --git a/package-lock.json b/package-lock.json
index a58d3d17..02ef0408 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2140,6 +2140,11 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
+ "@types/lodash": {
+ "version": "4.14.170",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
+ "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
+ },
"@types/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
@@ -3951,6 +3956,11 @@
}
}
},
+ "classnames": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+ },
"clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -6937,6 +6947,27 @@
"mime-types": "^2.1.12"
}
},
+ "formik": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.7.tgz",
+ "integrity": "sha512-j4cso6QL90T8hJWgU29GYsBQuj1vynBfrcURyK91KshArvS5CLoxUkP52hKc3wVpGFACd0uWEJo7y30ZOTzc5g==",
+ "requires": {
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.14",
+ "lodash-es": "^4.17.14",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
+ }
+ }
+ },
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -9433,6 +9464,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@@ -9914,6 +9950,11 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"optional": true
},
+ "nanoclone": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
+ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
+ },
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
@@ -11938,6 +11979,11 @@
}
}
},
+ "property-expr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
+ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
+ },
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
@@ -12333,6 +12379,16 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
"integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew=="
},
+ "react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+ },
+ "react-icons": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz",
+ "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ=="
+ },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -14793,6 +14849,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
+ "toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+ },
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
@@ -15267,8 +15328,7 @@
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "optional": true
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": {
"version": "2.3.0",
@@ -16880,6 +16940,20 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+ },
+ "yup": {
+ "version": "0.32.9",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
+ "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
+ "requires": {
+ "@babel/runtime": "^7.10.5",
+ "@types/lodash": "^4.14.165",
+ "lodash": "^4.17.20",
+ "lodash-es": "^4.17.15",
+ "nanoclone": "^0.2.1",
+ "property-expr": "^2.0.4",
+ "toposort": "^2.0.2"
+ }
}
}
}
diff --git a/package.json b/package.json
index 326aeb06..ca03b706 100644
--- a/package.json
+++ b/package.json
@@ -16,16 +16,21 @@
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.5",
"bootstrap": "^4.6.0",
+ "classnames": "^2.3.1",
+ "formik": "^2.2.7",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-icons": "^4.2.0",
"react-redux": "^7.2.3",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"sass": "^1.32.11",
- "web-vitals": "^1.1.1"
+ "uuid": "^8.3.2",
+ "web-vitals": "^1.1.1",
+ "yup": "^0.32.9"
},
"devDependencies": {
"@babel/core": "^7.13.16",
diff --git a/src/App.js b/src/App.js
index de524524..88704047 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,15 +1,215 @@
-import React from "react";
-
-function App() {
- return (
-
-
-
- );
+import React, { Component } from "react";
+import classNames from "classnames";
+import { Route } from "react-router-dom";
+import { v4 as uuidv4 } from "uuid";
+
+import TodoList from "./components/TodoList";
+import * as api from "./api";
+import AppHeader from "./components/AppHeader";
+
+import { HOME, ACTIVE, COMPLETED } from "./constatnts/routes";
+
+import "./app.scss";
+
+const LOCAL_STORAGE_KEY = "todo-state";
+
+function loadLocalStorageData() {
+ const prevItems = localStorage.getItem(LOCAL_STORAGE_KEY);
+
+ if (!prevItems) {
+ return null;
+ }
+
+ try {
+ return JSON.parse(prevItems);
+ } catch (error) {
+ return null;
+ }
+}
+
+class App extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ todos: [],
+ todoName: "",
+ currentTheme: false,
+ };
+ this.handleAddTodo = this.handleAddTodo.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+ this.handleChangeCheck = this.handleChangeCheck.bind(this);
+ this.handleEdit = this.handleEdit.bind(this);
+ this.handleEditSubmit = this.handleEditSubmit.bind(this);
+ this.handleResetEdit = this.handleResetEdit.bind(this);
+ this.handleClearCompleted = this.handleClearCompleted.bind(this);
+ this.handleThemeClick = this.handleThemeClick.bind(this);
+ }
+
+ componentDidMount() {
+ const prevItems = loadLocalStorageData();
+
+ if (!prevItems || !prevItems.todos.length) {
+ api.getProducts().then((data) => {
+ this.setState({ todos: data });
+ });
+ return;
+ }
+
+ this.setState({
+ todos: prevItems.todos,
+ currentTheme: prevItems.currentTheme,
+ });
+ }
+
+ componentDidUpdate() {
+ const { todos, currentTheme } = this.state;
+ localStorage.setItem(
+ LOCAL_STORAGE_KEY,
+ JSON.stringify({ todos, currentTheme }),
+ );
+ }
+
+ handleAddTodo(values) {
+ const { todos } = this.state;
+
+ const newTodo = {
+ id: uuidv4(),
+ name: values.name,
+ complete: false,
+ };
+
+ this.setState({ todos: [...todos, newTodo], todoName: "" });
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ this.handleAddTodo(this.state);
+ }
+
+ handleChange(e) {
+ this.setState({ todoName: e.target.value });
+ }
+
+ handleRemove(id) {
+ const { todos } = this.state;
+ const arr = todos.filter((todo) => todo.id !== id);
+ this.setState({ todos: arr });
+ }
+
+ handleChangeCheck(id) {
+ const { todos } = this.state;
+ const arr = todos.map((todo) => {
+ return todo.id === id ? { ...todo, complete: !todo.complete } : todo;
+ // return obj;
+ });
+
+ this.setState({ todos: arr });
+ }
+
+ handleClearCompleted() {
+ const { todos } = this.state;
+ const arr = todos.filter((todo) => todo.complete === false);
+ this.setState({ todos: arr });
+ }
+
+ handleEdit(id) {
+ const { todos } = this.state;
+ const todoToEdit = todos.map((todo) => {
+ return todo.id === id ? { ...todo, edit: true } : todo;
+ });
+ this.setState({ todos: todoToEdit });
+ }
+
+ handleEditSubmit(values, id) {
+ const { todos } = this.state;
+ const todoToEdit = todos.map((todo) => {
+ return todo.id === id
+ ? { ...todo, name: values.name, edit: false }
+ : todo;
+ });
+ this.setState({ todos: todoToEdit });
+ }
+
+ handleResetEdit() {
+ const { todos } = this.state;
+ const todoToEdit = todos.map((todo) => {
+ return { ...todo, edit: false };
+ });
+ this.setState({ todos: todoToEdit });
+ }
+
+ handleThemeClick() {
+ const { currentTheme } = this.state;
+ this.setState({ currentTheme: !currentTheme });
+ }
+
+ render() {
+ const { todos, currentTheme } = this.state;
+ const appClasses = classNames({
+ globalContainer: true,
+ darkModeOpacity: currentTheme,
+ });
+
+ return (
+
+
+
+
(
+ !todo.complete)}
+ handleClearCompleted={this.handleClearCompleted}
+ currentTheme={currentTheme}
+ />
+ )}
+ />
+
+ (
+ todo.complete)}
+ handleClearCompleted={this.handleClearCompleted}
+ currentTheme={currentTheme}
+ />
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ );
+ }
}
export default App;
diff --git a/src/api/getProducts.js b/src/api/getProducts.js
new file mode 100644
index 00000000..17377d03
--- /dev/null
+++ b/src/api/getProducts.js
@@ -0,0 +1,15 @@
+import todos from "../utils/demo-data";
+
+function getProducts(fail = false) {
+ return new Promise((res, rej) => {
+ setTimeout(() => {
+ if (fail) {
+ rej(new Error("Failed to fetch"));
+ }
+
+ res(todos);
+ }, 100);
+ });
+}
+
+export { getProducts };
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 00000000..3e032830
--- /dev/null
+++ b/src/api/index.js
@@ -0,0 +1 @@
+export * from "./getProducts";
diff --git a/src/app.scss b/src/app.scss
new file mode 100644
index 00000000..e6686296
--- /dev/null
+++ b/src/app.scss
@@ -0,0 +1,33 @@
+//light theme
+$bg-theme-light: #fafafa;
+$color-theme-light: #222;
+
+//dark theme_color
+$bg-theme-dark: #222;
+$color-theme-dark: #fff;
+
+body {
+ background-color: $bg-theme-light;
+ color: $color-theme-light;
+ height: 100vh;
+}
+
+* {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+ // outline: 1px solid black;
+}
+
+.globalContainer {
+ min-height: 100vh;
+}
+
+.darkMode {
+ background-color: rgba($color: $bg-theme-dark, $alpha: 1);
+ color: $color-theme-dark;
+}
+.darkModeOpacity {
+ background-color: rgba($color: $bg-theme-dark, $alpha: 0.6);
+ color: $color-theme-dark;
+}
diff --git a/src/components/AppFooter/AppFooter.js b/src/components/AppFooter/AppFooter.js
new file mode 100644
index 00000000..5edbeaa5
--- /dev/null
+++ b/src/components/AppFooter/AppFooter.js
@@ -0,0 +1,57 @@
+import React from "react";
+import { NavLink } from "react-router-dom";
+
+import "./AppFooter.scss";
+import classNames from "classnames";
+
+import { HOME, ACTIVE, COMPLETED } from "../../constatnts/routes";
+
+function AppFooter({ todos, handleClearCompleted, currentTheme }) {
+ function onHandleClearCompleted() {
+ handleClearCompleted();
+ }
+ const footerClearCompleteClasses = classNames({
+ main__footer__link: true,
+ main__footer__linkDarkMode: currentTheme,
+ });
+ return (
+
+ {todos.filter((v) => !v.complete).length} items left
+
+
+ All
+
+
+ Active
+
+
+ Completed
+
+
+
+
+ );
+}
+
+export default AppFooter;
diff --git a/src/components/AppFooter/AppFooter.scss b/src/components/AppFooter/AppFooter.scss
new file mode 100644
index 00000000..a891e9b8
--- /dev/null
+++ b/src/components/AppFooter/AppFooter.scss
@@ -0,0 +1,25 @@
+.main__footer {
+ &__link {
+ text-decoration: none;
+ color: black;
+ opacity: 0.7;
+ }
+
+ &__link:hover {
+ text-decoration: none;
+ color: rgba($color: #6c63ff, $alpha: 1);
+ }
+}
+
+.selected {
+ color: rgba($color: #6c63ff, $alpha: 1);
+ font-weight: bold;
+}
+
+@media only screen and (max-width: 500px) {
+ .main__footer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+}
diff --git a/src/components/AppFooter/index.js b/src/components/AppFooter/index.js
new file mode 100644
index 00000000..569644b5
--- /dev/null
+++ b/src/components/AppFooter/index.js
@@ -0,0 +1 @@
+export { default } from "./AppFooter";
diff --git a/src/components/AppHeader/AppHeader.js b/src/components/AppHeader/AppHeader.js
new file mode 100644
index 00000000..030dfaef
--- /dev/null
+++ b/src/components/AppHeader/AppHeader.js
@@ -0,0 +1,81 @@
+import React from "react";
+import { Formik } from "formik";
+import classNames from "classnames";
+
+import Checkbox from "../Checkbox";
+import Input from "../Input";
+import ThemeToggle from "../ThemeToggle";
+
+import productSchema from "./todo-schema";
+
+import hero from "../../img/hero.jpg";
+import "./AppHeader.scss";
+
+function Appheader({ handleAddTodo, handleThemeClick, currentTheme }) {
+ const headerClasses = classNames({
+ TODO__Header: true,
+ TODO__Header__DarkMode: currentTheme,
+ });
+
+ const formClasses = classNames({
+ TODO__Form: true,
+ TODO__Form__DarkMode: currentTheme,
+ });
+
+ const heroClasses = classNames({
+ heroImg: true,
+ heroImg__darkMode: currentTheme,
+ });
+
+ return (
+
+
+
+
TODO
+
+
+
{
+ handleAddTodo(values);
+ resetForm();
+ }}
+ >
+ {({
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ errors,
+ values,
+ touched,
+ }) => (
+
+ )}
+
+
+
+ );
+}
+
+export default Appheader;
diff --git a/src/components/AppHeader/AppHeader.scss b/src/components/AppHeader/AppHeader.scss
new file mode 100644
index 00000000..9e5b321f
--- /dev/null
+++ b/src/components/AppHeader/AppHeader.scss
@@ -0,0 +1,56 @@
+.heroImg {
+ min-height: 210px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(
+ 135deg,
+ hsl(348, 97%, 63%, 0.65),
+ hsl(230, 96%, 62%, 0.65)
+ ),
+ url("../../img/hero.jpg");
+ background-position: center;
+}
+
+.heroImg__darkMode {
+ background: linear-gradient(
+ 135deg,
+ rgba(241, 66, 101, 0.65),
+ rgba(7, 11, 27, 0.65)
+ ),
+ url("../../img/hero.jpg");
+ background-position: center;
+}
+
+.TODO__Header {
+ margin: 0px;
+ width: 100%;
+ max-width: 500px;
+ flex-grow: 1;
+ display: inline;
+ text-align: left;
+ letter-spacing: 10px;
+ color: white;
+}
+.TODO__Header__DarkMode {
+ color: #222;
+}
+.TODO__Form {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: white;
+ margin-bottom: 3rem;
+ border-radius: 5px;
+ overflow: hidden;
+ padding-left: 0.7rem;
+ width: 100%;
+ max-width: 500px;
+}
+
+.TODO__Form__DarkMode {
+ background-color: #222;
+ color: white;
+}
diff --git a/src/components/AppHeader/index.js b/src/components/AppHeader/index.js
new file mode 100644
index 00000000..8ad0d174
--- /dev/null
+++ b/src/components/AppHeader/index.js
@@ -0,0 +1 @@
+export { default } from "./AppHeader";
diff --git a/src/components/AppHeader/todo-schema.js b/src/components/AppHeader/todo-schema.js
new file mode 100644
index 00000000..7e25179f
--- /dev/null
+++ b/src/components/AppHeader/todo-schema.js
@@ -0,0 +1,10 @@
+import * as Yup from "yup";
+
+const productSchema = Yup.object().shape({
+ name: Yup.string()
+ .required("The todo name is required")
+ .min(2, "The todo name is too short!")
+ .max(50, "The todo name is too long!"),
+});
+
+export default productSchema;
diff --git a/src/components/Checkbox/Checkbox.js b/src/components/Checkbox/Checkbox.js
new file mode 100644
index 00000000..51536f5a
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.js
@@ -0,0 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import React from "react";
+import "./Checkbox.scss";
+
+export default function Checkbox({ checked, handleChangeCheck, id }) {
+ return (
+ handleChangeCheck(id)}>
+
+
+ );
+}
diff --git a/src/components/Checkbox/Checkbox.scss b/src/components/Checkbox/Checkbox.scss
new file mode 100644
index 00000000..4ea6bed4
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.scss
@@ -0,0 +1,41 @@
+.border {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 0 8px;
+ width: 20px;
+ height: 20px;
+ border: 2px solid grey;
+ border-radius: 25px;
+}
+
+.indicator {
+ position: relative;
+ width: 15px;
+ height: 15px;
+ border-radius: 25px;
+ z-index: 1;
+ background: linear-gradient(
+ 135deg,
+ hsl(348, 97%, 63%, 1),
+ hsl(230, 96%, 62%, 1)
+ );
+ transform: scale(0);
+ transition: transform 100ms;
+}
+
+.indicator::after {
+ position: absolute;
+ top: 40%;
+ left: 50%;
+ width: 5px;
+ height: 9px;
+ content: "";
+ border-right: 2px solid white;
+ border-bottom: 2px solid white;
+ transform: translate(-50%, -50%) rotate(40deg);
+}
+.checked {
+ transform: scale(1.1);
+}
diff --git a/src/components/Checkbox/index.js b/src/components/Checkbox/index.js
new file mode 100644
index 00000000..a936c852
--- /dev/null
+++ b/src/components/Checkbox/index.js
@@ -0,0 +1 @@
+export { default } from "./Checkbox";
diff --git a/src/components/EditTodo/EditTodo.js b/src/components/EditTodo/EditTodo.js
new file mode 100644
index 00000000..11a20807
--- /dev/null
+++ b/src/components/EditTodo/EditTodo.js
@@ -0,0 +1,51 @@
+/* eslint-disable react/jsx-boolean-value */
+import React from "react";
+import { Formik } from "formik";
+
+import Input from "../Input";
+import editSchema from "./edit-schema";
+
+import "./EditTodo.scss";
+
+function EditTodo({ handleEditSubmit, handleResetEdit, todo, currentTheme }) {
+ return (
+ {
+ handleEditSubmit(values, todo.id);
+ }}
+ >
+ {({
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ errors,
+ values,
+ touched,
+ }) => (
+
+ )}
+
+ );
+}
+
+export default EditTodo;
diff --git a/src/components/EditTodo/EditTodo.scss b/src/components/EditTodo/EditTodo.scss
new file mode 100644
index 00000000..9a07f62b
--- /dev/null
+++ b/src/components/EditTodo/EditTodo.scss
@@ -0,0 +1,5 @@
+.Edit__Form {
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+}
diff --git a/src/components/EditTodo/edit-schema.js b/src/components/EditTodo/edit-schema.js
new file mode 100644
index 00000000..e92813fc
--- /dev/null
+++ b/src/components/EditTodo/edit-schema.js
@@ -0,0 +1,10 @@
+import * as Yup from "yup";
+
+const editSchema = Yup.object().shape({
+ name: Yup.string()
+ .required("The todo name is required")
+ .min(2, "The todo name is too short!")
+ .max(50, "The todo name is too long!"),
+});
+
+export default editSchema;
diff --git a/src/components/EditTodo/index.js b/src/components/EditTodo/index.js
new file mode 100644
index 00000000..17c94385
--- /dev/null
+++ b/src/components/EditTodo/index.js
@@ -0,0 +1 @@
+export { default } from "./EditTodo";
diff --git a/src/components/EmptyTodo/EmptyTodo.js b/src/components/EmptyTodo/EmptyTodo.js
new file mode 100644
index 00000000..065ab95a
--- /dev/null
+++ b/src/components/EmptyTodo/EmptyTodo.js
@@ -0,0 +1,15 @@
+import React from "react";
+
+import illustration from "../../img/illustration.svg";
+import "./EmptyTodo.scss";
+
+function EmptyTodo() {
+ return (
+
+
There is no todo...
+

+
+ );
+}
+
+export default EmptyTodo;
diff --git a/src/components/EmptyTodo/EmptyTodo.scss b/src/components/EmptyTodo/EmptyTodo.scss
new file mode 100644
index 00000000..3c6e09c5
--- /dev/null
+++ b/src/components/EmptyTodo/EmptyTodo.scss
@@ -0,0 +1,7 @@
+.main__empty {
+ p {
+ color: rgba($color: #6c63ff, $alpha: 1);
+ font-size: 1.3rem;
+ font-weight: bold;
+ }
+}
diff --git a/src/components/EmptyTodo/index.js b/src/components/EmptyTodo/index.js
new file mode 100644
index 00000000..97fec86c
--- /dev/null
+++ b/src/components/EmptyTodo/index.js
@@ -0,0 +1 @@
+export { default } from "./EmptyTodo";
diff --git a/src/components/Input/Input.js b/src/components/Input/Input.js
new file mode 100644
index 00000000..f30b942f
--- /dev/null
+++ b/src/components/Input/Input.js
@@ -0,0 +1,48 @@
+/* eslint-disable jsx-a11y/no-autofocus */
+import classNames from "classnames";
+import React from "react";
+
+import "./Input.scss";
+
+function Input({
+ type = "text",
+ id = "input-01",
+ value = "",
+ placeholder = "",
+ handleChange = () => {},
+ handleBlur = () => {},
+ errorMessage,
+ hasErrorMessage,
+ autoFocus,
+ currentTheme,
+ ...props
+}) {
+ const classes = classNames({
+ input__class: true,
+ "input-class-err": hasErrorMessage && errorMessage,
+ "is-invalid": hasErrorMessage && errorMessage,
+ input__class__darkMode: currentTheme,
+ });
+
+ return (
+ <>
+
+ {hasErrorMessage && errorMessage && (
+ {errorMessage}
+ )}
+ >
+ );
+}
+
+export default Input;
diff --git a/src/components/Input/Input.scss b/src/components/Input/Input.scss
new file mode 100644
index 00000000..6d656e57
--- /dev/null
+++ b/src/components/Input/Input.scss
@@ -0,0 +1,21 @@
+.invalid-msg {
+ color: red;
+ font-size: 80%;
+ margin: 0;
+ padding: 0;
+}
+
+.input__class {
+ flex-grow: 1;
+ padding: 0.7rem;
+ border: none;
+ outline: none;
+ background: transparent;
+}
+
+.input__class__darkMode {
+ color: white;
+}
+.input-class-err {
+ flex-grow: 0;
+}
diff --git a/src/components/Input/index.js b/src/components/Input/index.js
new file mode 100644
index 00000000..a50d7d11
--- /dev/null
+++ b/src/components/Input/index.js
@@ -0,0 +1 @@
+export { default } from "./Input";
diff --git a/src/components/ThemeToggle/ThemeToggle.js b/src/components/ThemeToggle/ThemeToggle.js
new file mode 100644
index 00000000..2e736264
--- /dev/null
+++ b/src/components/ThemeToggle/ThemeToggle.js
@@ -0,0 +1,36 @@
+import React from "react";
+import classNames from "classnames";
+
+import "./ThemeToggle.scss";
+
+function ThemeToggle({ handleThemeClick, currentTheme }) {
+ function onHandleThemeClick() {
+ handleThemeClick();
+ }
+ const themeSwitcherClasses = classNames({
+ themeSwitcherButton: true,
+ themeSwitcherButton__active: currentTheme,
+ });
+
+ return (
+
+ );
+}
+
+export default ThemeToggle;
diff --git a/src/components/ThemeToggle/ThemeToggle.scss b/src/components/ThemeToggle/ThemeToggle.scss
new file mode 100644
index 00000000..b186ee53
--- /dev/null
+++ b/src/components/ThemeToggle/ThemeToggle.scss
@@ -0,0 +1,47 @@
+.themeToggle {
+ display: flex;
+}
+
+.themeSwitcherWrap {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ max-width: 500px;
+ margin-bottom: 20px;
+
+ .themeSwitcherButton {
+ margin: 0px 3px;
+ margin-top: 3px;
+ display: inline-block;
+ max-height: 20px;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+
+ .switchPath {
+ width: 40px;
+ height: 20px;
+ border-radius: 10px;
+ background-color: #ccc;
+ transition: all 0.3s ease-in-out;
+
+ .switchHandle {
+ background-color: #999;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ transition: all 0.3s ease-in-out;
+ }
+ }
+
+ &__active {
+ .switchPath {
+ background-color: #555;
+ }
+ .switchHandle {
+ transform: translateX(20px);
+ }
+ }
+ }
+}
diff --git a/src/components/ThemeToggle/index.js b/src/components/ThemeToggle/index.js
new file mode 100644
index 00000000..b3c9211c
--- /dev/null
+++ b/src/components/ThemeToggle/index.js
@@ -0,0 +1 @@
+export { default } from "./ThemeToggle";
diff --git a/src/components/Todo/Todo.js b/src/components/Todo/Todo.js
new file mode 100644
index 00000000..b57210c9
--- /dev/null
+++ b/src/components/Todo/Todo.js
@@ -0,0 +1,71 @@
+import classNames from "classnames";
+import React from "react";
+import { AiOutlineClose } from "react-icons/ai";
+
+import Checkbox from "../Checkbox";
+import EditTodo from "../EditTodo";
+
+import "./Todo.scss";
+
+function Todo({
+ todo = {},
+ handleRemove = () => {},
+ handleChangeCheck = () => {},
+ handleEdit = () => {},
+ handleEditSubmit = () => {},
+ handleResetEdit,
+ currentTheme,
+}) {
+ function onHandleRemove() {
+ handleRemove(todo.id);
+ }
+ function onHandleEdit() {
+ handleEdit(todo.id);
+ }
+
+ const mainTodoClasses = classNames({
+ main__todo: true,
+ main__todo__darkMode: currentTheme,
+ });
+
+ const completedTask = classNames({
+ completed: todo.complete,
+ darkMode: currentTheme,
+ });
+
+ return (
+
+
+
+ {todo.edit ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
+
+export default Todo;
diff --git a/src/components/Todo/Todo.scss b/src/components/Todo/Todo.scss
new file mode 100644
index 00000000..dfbebc53
--- /dev/null
+++ b/src/components/Todo/Todo.scss
@@ -0,0 +1,40 @@
+.main__todo {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ padding: 0 0.7rem;
+
+ &__close {
+ opacity: 0.7;
+ display: none;
+ }
+
+ &__check {
+ display: flex;
+ align-items: center;
+ width: inherit;
+
+ button {
+ margin: 0;
+ padding: 0.7rem;
+ background: transparent;
+ border: none;
+ flex-grow: 1;
+ text-align: start;
+ }
+ }
+}
+
+.main__todo:hover .main__todo__close {
+ display: block;
+}
+
+.main__todo__darkMode {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.7);
+}
+.completed {
+ opacity: 0.5;
+ text-decoration: line-through;
+}
diff --git a/src/components/Todo/index.js b/src/components/Todo/index.js
new file mode 100644
index 00000000..012972be
--- /dev/null
+++ b/src/components/Todo/index.js
@@ -0,0 +1 @@
+export { default } from "./Todo";
diff --git a/src/components/TodoList/TodoList.js b/src/components/TodoList/TodoList.js
new file mode 100644
index 00000000..2664d1ca
--- /dev/null
+++ b/src/components/TodoList/TodoList.js
@@ -0,0 +1,54 @@
+import React from "react";
+// import { v4 as uuidv4 } from "uuid";
+import classNames from "classnames";
+
+import AppFooter from "../AppFooter";
+import EmptyTodo from "../EmptyTodo";
+import Todo from "../Todo";
+
+import "./TodoList.scss";
+
+function TodoList({
+ todos = [],
+ handleRemove,
+ handleChangeCheck,
+ handleEdit,
+ handleEditSubmit,
+ handleResetEdit,
+ handleClearCompleted,
+ currentTheme,
+}) {
+ function printTodos() {
+ return todos.map((todo) => (
+
+ ));
+ }
+
+ const todoListClasses = classNames({
+ main: true,
+ darkMode: currentTheme,
+ });
+
+ return (
+
+ {todos.length ? printTodos() : }
+
+
+
+ );
+}
+
+export default TodoList;
diff --git a/src/components/TodoList/TodoList.scss b/src/components/TodoList/TodoList.scss
new file mode 100644
index 00000000..79f3ae9e
--- /dev/null
+++ b/src/components/TodoList/TodoList.scss
@@ -0,0 +1,47 @@
+.main {
+ max-width: 500px;
+ min-height: 492px;
+ background-color: white;
+ border-radius: 5px;
+ box-shadow: 0px 0px 15px -1px rgba(0, 0, 0, 0.2);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ margin: 0 auto;
+ margin-top: -45px;
+
+ &__empty {
+ p {
+ margin: 0.4rem auto;
+ text-align: center;
+ }
+ img {
+ max-width: 100%;
+ }
+ }
+
+ &__footer {
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+ display: flex;
+ justify-content: space-around;
+
+ &__lwrp {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &__link {
+ border: none;
+ background-color: transparent;
+ padding: 0.4rem;
+ }
+ &__linkDarkMode {
+ color: white;
+ }
+ span {
+ padding: 0.3rem 0.2rem;
+ opacity: 0.4;
+ }
+ }
+}
diff --git a/src/components/TodoList/index.js b/src/components/TodoList/index.js
new file mode 100644
index 00000000..8d439448
--- /dev/null
+++ b/src/components/TodoList/index.js
@@ -0,0 +1 @@
+export { default } from "./TodoList";
diff --git a/src/constatnts/routes.js b/src/constatnts/routes.js
new file mode 100644
index 00000000..c5654cb9
--- /dev/null
+++ b/src/constatnts/routes.js
@@ -0,0 +1,3 @@
+export const HOME = "/";
+export const ACTIVE = "/active";
+export const COMPLETED = "/completed";
diff --git a/src/img/hero.jpg b/src/img/hero.jpg
new file mode 100644
index 00000000..c374a896
Binary files /dev/null and b/src/img/hero.jpg differ
diff --git a/src/img/illustration.svg b/src/img/illustration.svg
new file mode 100644
index 00000000..20aa77e2
--- /dev/null
+++ b/src/img/illustration.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 19bb154c..c4f7c38d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
+import { BrowserRouter } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
@@ -8,7 +9,9 @@ import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
-
+
+
+
,
document.getElementById("root"),
);
diff --git a/src/utils/demo-data.js b/src/utils/demo-data.js
new file mode 100644
index 00000000..5225edaf
--- /dev/null
+++ b/src/utils/demo-data.js
@@ -0,0 +1,8 @@
+const todos = [
+ { id: 1, name: "Brahim Benalia Casas", complete: false },
+ { id: 2, name: "Marc Solá Crack", complete: false },
+ { id: 3, name: "Brahim Benalia Casas", complete: false },
+ { id: 4, name: "Marc Solá Crack", complete: false },
+];
+
+export default todos;