diff --git a/.eslintrc.js b/.eslintrc.js index 17514e75..4178d12e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,7 +49,12 @@ module.exports = { }, ], rules: { - "prettier/prettier": "error", + "prettier/prettier": [ + "error", + { + endOfLine: "auto", + }, + ], "react/jsx-filename-extension": "off", "import/prefer-default-export": "off", "prefer-destructuring": "off", diff --git a/package-lock.json b/package-lock.json index a58d3d17..80659b95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1310,6 +1310,51 @@ } } }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", + "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", + "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.3.tgz", + "integrity": "sha512-1hirPcbjj72ZJtFvdnXGPbAbpn3Ox6mH3g5STbANFp3vGSiE5u5ingAKV06mK6ZVqNYxUPlh4DlTnaIvLtF2kw==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz", + "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", + "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", + "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -2140,6 +2185,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/lodash": { + "version": "4.14.169", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz", + "integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw==" + }, "@types/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", @@ -6937,6 +6987,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 +9504,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 +9990,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 +12019,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 +12419,11 @@ "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-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -14793,6 +14884,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 +15363,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 +16975,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..b984df4e 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,16 @@ "private": true, "main": "src/index.js", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-brands-svg-icons": "^5.15.3", + "@fortawesome/free-regular-svg-icons": "^5.15.3", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.1.14", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^13.1.5", "bootstrap": "^4.6.0", + "formik": "^2.2.7", "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -25,7 +31,9 @@ "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..f8f38a8e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,15 +1,321 @@ -import React from "react"; - -function App() { - return ( -
-
-
-

Hola mundo

-
-
-
- ); +import React, { Component } from "react"; +import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; +import "./_text.scss"; +import "./styles.scss"; +import "./components/Footer/Footer.scss"; + +import Header from "./components/Header"; +import NewTodo from "./components/NewTodo"; +import TodoList from "./components/todolist"; + +import * as api from "./api"; + +// import * as api from "./api"; + +const LOCAL_STORAGE_KEY = "react-todo-app"; + +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: [], + }; + this.handleIsActive = this.handleIsActive.bind(this); + this.saveNewTodo = this.saveNewTodo.bind(this); + this.clearCompleted = this.clearCompleted.bind(this); + this.deleteTodo = this.deleteTodo.bind(this); + this.handleIsEdit = this.handleIsEdit.bind(this); + this.editTodo = this.editTodo.bind(this); + this.displayHashtag = this.displayHashtag.bind(this); + this.changeHashtag = this.changeHashtag.bind(this); + this.toggleDarkMode = this.toggleDarkMode.bind(this); + } + + componentDidMount() { + const prevItems = loadLocalStorageData(); + + if (!prevItems) { + this.setState({ + todos: [], + }); + + api.getTodos().then((data) => { + this.setState({ + todos: data, + }); + }); + return; + } + + this.setState({ + darkMode: prevItems.darkMode, + todos: prevItems.todos, + }); + } + + componentDidUpdate() { + const { todos, darkMode } = this.state; + localStorage.setItem( + LOCAL_STORAGE_KEY, + JSON.stringify({ todos, darkMode }), + ); + } + + handleIsActive(todosId) { + const { todos } = this.state; + + const updatedTodos = todos.map((todo) => { + if (todo.id === todosId) { + return { + ...todo, + isActive: !todo.isActive, + }; + } + + return todo; + }); + + this.setState({ todos: updatedTodos }); + } + + handleIsEdit(todoId) { + const { todos } = this.state; + + const updatedTodos = todos.map((todo) => { + if (todo.id === todoId) { + return { + ...todo, + isEdit: !todo.isEdit, + }; + } + + return todo; + }); + + this.setState({ todos: updatedTodos }); + } + + saveNewTodo(newTodo) { + this.setState((prevState) => ({ + todos: [newTodo, ...prevState.todos], + })); + } + + deleteTodo(todoId) { + const { todos } = this.state; + const todoDelete = todos.filter((todo) => todo.id !== todoId); + this.setState(() => ({ + todos: todoDelete, + })); + } + + editTodo(todoId, content) { + const { todos } = this.state; + const updatedTodos = todos.map((todo) => { + if (todoId === todo.id) { + return { + ...todo, + content: content, + }; + } + return todo; + }); + + this.setState({ todos: updatedTodos }); + } + + clearCompleted() { + const { todos } = this.state; + const newTodos = todos.filter((todo) => todo.isActive === false); + this.setState(() => ({ + todos: newTodos, + })); + } + + displayHashtag(todoId) { + const { todos } = this.state; + + const updatedTodos = todos.map((todo) => { + if (todo.id === todoId) { + return { + ...todo, + hashtagDisplayed: !todo.hashtagDisplayed, + }; + } + + return todo; + }); + + this.setState({ todos: updatedTodos }); + } + + changeHashtag(todoId, color) { + const { todos } = this.state; + + const updatedTodos = todos.map((todo) => { + let updateColor; + if (todo.id === todoId) { + if (color === todo.hashtag) { + updateColor = ""; + } else { + updateColor = color; + } + return { + ...todo, + hashtag: updateColor, + }; + } + + return todo; + }); + + this.setState({ todos: updatedTodos }); + } + + toggleDarkMode() { + const { darkMode } = this.state; + // eslint-disable-next-line + console.log({ darkMode }); + this.setState({ darkMode: !darkMode }); + } + + render() { + const { todos } = this.state; + const completed = todos.filter((todo) => todo.isActive === true); + const active = todos.filter((todo) => todo.isActive === false); + const { darkMode } = this.state; + + return ( + <> + +
+
+
+
+ +
+
+ + + + + + + + + + + + +
+
+
+
+ + ); + } } export default App; diff --git a/src/_colors.scss b/src/_colors.scss new file mode 100644 index 00000000..b1f02f03 --- /dev/null +++ b/src/_colors.scss @@ -0,0 +1,2 @@ +$body-background: rgb(255, 255, 255); +$main-background: rgb(255, 255, 255); diff --git a/src/_text.scss b/src/_text.scss new file mode 100644 index 00000000..a91b0802 --- /dev/null +++ b/src/_text.scss @@ -0,0 +1,30 @@ +@import url("https://fonts.googleapis.com/css2?family=Signika&display=swap"); + +.text__normal--check { + font-family: Arial, Helvetica, sans-serif; + font-size: 18px; + color: rgb(161, 161, 161); +} +.text__normal { + font-family: Arial, Helvetica, sans-serif; + font-size: 18px; + color: rgb(66, 66, 66); +} + +.text__small { + font-family: Arial, Helvetica, sans-serif; + font-size: 11px; + color: rgb(88, 88, 88); + cursor: pointer; + + &.dark { + color: #9499e9; + } +} +.text__small:hover { + color: rgb(165, 165, 165); + + &.dark { + color: #44488d; + } +} diff --git a/src/api/getTodos.js b/src/api/getTodos.js new file mode 100644 index 00000000..a263d908 --- /dev/null +++ b/src/api/getTodos.js @@ -0,0 +1,15 @@ +import todos from "./todos"; + +function getTodos(fail = false) { + return new Promise((res, rej) => { + setTimeout(() => { + if (fail) { + rej(new Error("Failed to fetch")); + } + + res(todos); + }, 1000); + }); +} + +export { getTodos }; diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 00000000..e0d8be26 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1 @@ +export * from "./getTodos"; diff --git a/src/api/todos.js b/src/api/todos.js new file mode 100644 index 00000000..60fd4f26 --- /dev/null +++ b/src/api/todos.js @@ -0,0 +1,68 @@ +const todos = [ + { + id: "65d6b269-209b-50ac-a566-cjha334aa6f0", + isActive: true, + isEdit: false, + hashtagDisplayed: false, + hashtag: "green", + content: "Replace .test from styles.scss", + }, + { + id: "fbabb4ce-823f-5790-5687-35819d4be380", + isActive: true, + isEdit: false, + hashtagDisplayed: false, + hashtag: "red", + content: "Fix localStorage and componentDidUpdate", + }, + { + id: "5e88d7ab-ecdd-5a92-uy4a-7391a4709994", + isActive: false, + isEdit: false, + hashtagDisplayed: false, + hashtag: "red", + content: "Prepare everything for DB implementation", + }, + { + id: "3ca3c877-0d1c-585e-8we6-e19a97392c93", + isActive: false, + isEdit: false, + hashtagDisplayed: false, + hashtag: "yellow", + content: "Ask for a beer helping", + }, + { + id: "208c1e43-c945-58er-9469-19d9229fded4", + isActive: false, + isEdit: false, + hashtagDisplayed: false, + hashtag: "red", + content: "Siesta time", + }, + { + id: "10691cf1-28ce-fgc7-8eac-812e8eb623cb", + isActive: true, + isEdit: false, + hashtagDisplayed: false, + hashtag: "green", + content: "Work work work", + }, + { + id: "208c1e43-c945-5876-9469-19d9229fded4", + isActive: false, + isEdit: false, + hashtagDisplayed: false, + hashtag: "red", + content: "Siesta time", + }, + { + id: "10691cf1-28ce-5ec7-8e54-812e8eb623cb", + isActive: true, + isEdit: false, + hashtagDisplayed: false, + hashtag: "green", + content: "Work work work", + }, +]; + +export default todos; diff --git a/src/components/Footer/Footer.scss b/src/components/Footer/Footer.scss new file mode 100644 index 00000000..739fa675 --- /dev/null +++ b/src/components/Footer/Footer.scss @@ -0,0 +1,14 @@ +footer { + display: flex; + flex-direction: row; + justify-content: space-between; + align-content: center; + padding: 0.8rem; +} + +button { + border: none; + background-color: transparent; + margin: 0 0.5rem; + cursor: pointer; +} diff --git a/src/components/Hashtag/Hashtag.js b/src/components/Hashtag/Hashtag.js new file mode 100644 index 00000000..0dc19113 --- /dev/null +++ b/src/components/Hashtag/Hashtag.js @@ -0,0 +1,45 @@ +import React from "react"; + +import "./Hashtag.scss"; + +function Hashtag({ id, hashtagDisplayed, changeHashtag }) { + function changeRed() { + changeHashtag(id, "red"); + } + function changeYellow() { + changeHashtag(id, "yellow"); + } + function changeGreen() { + changeHashtag(id, "green"); + } + + return ( +
+ + + +
+ ); +} + +export default Hashtag; diff --git a/src/components/Hashtag/Hashtag.scss b/src/components/Hashtag/Hashtag.scss new file mode 100644 index 00000000..c5e04bb4 --- /dev/null +++ b/src/components/Hashtag/Hashtag.scss @@ -0,0 +1,41 @@ +.container__hashtag { + width: inherit; + display: none; + padding: 0; + flex-direction: row; + align-items: center; + justify-content: space-evenly; + cursor: pointer; + padding: 0.5rem; + transition: display 1s ease; +} + +.display-flex { + display: flex; +} + +.container__hashtag > div { + display: flex; + flex-direction: row; + align-items: center; +} + +.container__hashtag > div > div { + width: 40px; + height: 20px; + border-radius: 4px; + margin: 0 0.5rem; + //align-self: stretch; +} + +.hashtag { + align-self: stretch; +} + +.hashtagButton { + width: 20%; + color: white; + font-weight: bold; + padding: 0.2rem 0.5rem; + border-radius: 0.5rem; +} diff --git a/src/components/Hashtag/index.js b/src/components/Hashtag/index.js new file mode 100644 index 00000000..5a3f89c3 --- /dev/null +++ b/src/components/Hashtag/index.js @@ -0,0 +1 @@ +export { default } from "./Hashtag"; diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js new file mode 100644 index 00000000..6a1cbc53 --- /dev/null +++ b/src/components/Header/Header.js @@ -0,0 +1,21 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMoon } from "@fortawesome/free-solid-svg-icons"; +import "./Header.scss"; + +export default function Header({ toggleDarkMode, darkMode }) { + function onDarkMode() { + toggleDarkMode(); + } + return ( +
+

TODO

+ +
+ ); +} diff --git a/src/components/Header/Header.scss b/src/components/Header/Header.scss new file mode 100644 index 00000000..faa4f18d --- /dev/null +++ b/src/components/Header/Header.scss @@ -0,0 +1,34 @@ +.header { + width: 100%; + display: flex; + color: white; + font-size: 3rem; + font-weight: bold; + margin-bottom: 3rem; + + .title { + width: 90%; + letter-spacing: 0.8rem; + + &.dark { + color: #151a80; + } + } + + .dark-mode { + display: flex; + width: 10%; + justify-content: center; + align-items: center; + transform: scale(3); + } +} + +.moon { + transform: rotateY(180deg); + color: white; + + &.dark { + color: #151a80; + } +} diff --git a/src/components/Header/index.js b/src/components/Header/index.js new file mode 100644 index 00000000..2764567d --- /dev/null +++ b/src/components/Header/index.js @@ -0,0 +1 @@ +export { default } from "./Header"; diff --git a/src/components/NewTodo/NewTodo.js b/src/components/NewTodo/NewTodo.js new file mode 100644 index 00000000..a925fad5 --- /dev/null +++ b/src/components/NewTodo/NewTodo.js @@ -0,0 +1,86 @@ +import React from "react"; +import { v4 as uuid } from "uuid"; +import { Formik } from "formik"; + +import "./NewTodo.scss"; + +import newTodoSchema from "./NewTodoSchema"; + +import NewTodoInput from "../NewTodoInput"; + +function addTodoDetails(todo) { + return { + id: uuid(), + ...todo, + isActive: false, + isEdit: false, + hashtagDisplayed: false, + hashtag: "", + }; +} + +export default function NewTodo({ + saveNewTodo, + editTodo, + id, + content, + darkMode, +}) { + function onSave(values) { + editTodo(id, values.target.value); + } + const onSubmit = async ( + values, + { setSubmitting, setErrors, setStatus, resetForm }, + ) => { + try { + await saveNewTodo(addTodoDetails(values)); + resetForm({}); + setStatus({ success: true }); + } catch (error) { + setStatus({ success: false }); + setSubmitting(false); + setErrors({ submit: error.message }); + } + }; + return ( + + {({ + handleChange, + handleBlur, + handleSubmit, + errors, + values, + touched, + }) => ( +
+ + + )} +
+ ); +} diff --git a/src/components/NewTodo/NewTodo.scss b/src/components/NewTodo/NewTodo.scss new file mode 100644 index 00000000..331a2609 --- /dev/null +++ b/src/components/NewTodo/NewTodo.scss @@ -0,0 +1,26 @@ +.new-todo { + background-color: white; + border-radius: 8px; + display: flex; + align-items: center; + padding: 1rem; + width: 100%; + margin-bottom: 2rem; + justify-content: center; + box-shadow: 0px 5px 20px 3px rgba(128, 128, 128, 0.432); + + &.dark { + background-color: #121669; + color: white; + box-shadow: 0px 5px 20px 3px #535690; + } +} + +.new-todo-form { + width: 95%; + + &.dark { + background-color: #121669; + color: white; + } +} diff --git a/src/components/NewTodo/NewTodoSchema.js b/src/components/NewTodo/NewTodoSchema.js new file mode 100644 index 00000000..8d43432f --- /dev/null +++ b/src/components/NewTodo/NewTodoSchema.js @@ -0,0 +1,10 @@ +import * as Yup from "yup"; + +const newTodoSchema = Yup.object().shape({ + content: Yup.string() + .min(2, "The title is too short!") + .max(50, "The title is too long!") + .required("The title is required"), +}); + +export default newTodoSchema; diff --git a/src/components/NewTodo/index.js b/src/components/NewTodo/index.js new file mode 100644 index 00000000..9c49b53d --- /dev/null +++ b/src/components/NewTodo/index.js @@ -0,0 +1 @@ +export { default } from "./NewTodo"; diff --git a/src/components/NewTodoInput/NewTodoInput.js b/src/components/NewTodoInput/NewTodoInput.js new file mode 100644 index 00000000..95338769 --- /dev/null +++ b/src/components/NewTodoInput/NewTodoInput.js @@ -0,0 +1,34 @@ +import React from "react"; + +import "./NewTodoInput.scss"; + +export default function Input({ + type = "text", + id, + value = "", + placeholder = "", + handleChange, + handleBlur, + errorMessage, + hasErrorMessage, + darkMode, + ...props +}) { + return ( +
+ + {hasErrorMessage && errorMessage && ( +

{errorMessage}

+ )} +
+ ); +} diff --git a/src/components/NewTodoInput/NewTodoInput.scss b/src/components/NewTodoInput/NewTodoInput.scss new file mode 100644 index 00000000..9b17a2d8 --- /dev/null +++ b/src/components/NewTodoInput/NewTodoInput.scss @@ -0,0 +1,27 @@ +.input { + min-width: 90%; + width: 100%; + font-size: 1rem; + font-weight: 100; + height: 2rem; + border: none; + outline: none; + + &.dark { + background-color: #121669; + color: white; + } + + &.dark::placeholder { + background-color: #121669; + color: white; + } + + &.dark:-webkit-autofill { + transition: background-color 5000s; + -webkit-text-fill-color: white !important; + } +} +.input:hover { + border-bottom: #dadce0 2px solid; +} diff --git a/src/components/NewTodoInput/index.js b/src/components/NewTodoInput/index.js new file mode 100644 index 00000000..9ce21360 --- /dev/null +++ b/src/components/NewTodoInput/index.js @@ -0,0 +1 @@ +export { default } from "./NewTodoInput"; diff --git a/src/components/NoTodo/NoTodo.js b/src/components/NoTodo/NoTodo.js new file mode 100644 index 00000000..3b3fc9e8 --- /dev/null +++ b/src/components/NoTodo/NoTodo.js @@ -0,0 +1,26 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSmile } from "@fortawesome/free-solid-svg-icons"; +import image from "../../img/noTodo.svg"; +import "./NoTodo.scss"; + +function NoTodo({ darkMode }) { + return ( +
+ +
+ ); +} + +export default NoTodo; diff --git a/src/components/NoTodo/NoTodo.scss b/src/components/NoTodo/NoTodo.scss new file mode 100644 index 00000000..bc8c704f --- /dev/null +++ b/src/components/NoTodo/NoTodo.scss @@ -0,0 +1,38 @@ +.todo-list { + background-color: #fff; + border-radius: 8px 8px 0 0; + overflow: hidden; +} + +ul { + width: 100%; + list-style: none; +} + +li.img { + text-align: center; + width: auto; + height: auto; + padding: 0.5rem; + border-bottom: #dadce0 1.8px solid; +} +img { + width: 400px; + max-height: 400px; +} +.text { + color: #612fb5; + margin-bottom: 2rem; + + &.dark { + color: white; + } +} +.svg { + color: #612fb5; + font-size: 1.2rem; + + &.dark { + color: white; + } +} diff --git a/src/components/NoTodo/index.js b/src/components/NoTodo/index.js new file mode 100644 index 00000000..6e325fde --- /dev/null +++ b/src/components/NoTodo/index.js @@ -0,0 +1 @@ +export { default } from "./NoTodo"; diff --git a/src/components/TodoInput/TodoInput.js b/src/components/TodoInput/TodoInput.js new file mode 100644 index 00000000..842fe928 --- /dev/null +++ b/src/components/TodoInput/TodoInput.js @@ -0,0 +1,41 @@ +import React from "react"; +import "./TodoInput.scss"; + +export default function Input({ + type = "text", + id, + content, + handleChange, + handleBlur, + handleEnterKey, + errorMessage, + hasErrorMessage, + displayHashtag, + hashtagDisplayed, + darkMode, + ...props +}) { + function isDisplayed() { + if (id) { + displayHashtag(id); + } + } + return ( +
+ + {hasErrorMessage && errorMessage && ( +

{errorMessage}

+ )} +
+ ); +} diff --git a/src/components/TodoInput/TodoInput.scss b/src/components/TodoInput/TodoInput.scss new file mode 100644 index 00000000..9b17a2d8 --- /dev/null +++ b/src/components/TodoInput/TodoInput.scss @@ -0,0 +1,27 @@ +.input { + min-width: 90%; + width: 100%; + font-size: 1rem; + font-weight: 100; + height: 2rem; + border: none; + outline: none; + + &.dark { + background-color: #121669; + color: white; + } + + &.dark::placeholder { + background-color: #121669; + color: white; + } + + &.dark:-webkit-autofill { + transition: background-color 5000s; + -webkit-text-fill-color: white !important; + } +} +.input:hover { + border-bottom: #dadce0 2px solid; +} diff --git a/src/components/TodoInput/index.js b/src/components/TodoInput/index.js new file mode 100644 index 00000000..a2a1eed3 --- /dev/null +++ b/src/components/TodoInput/index.js @@ -0,0 +1 @@ +export { default } from "./TodoInput"; diff --git a/src/components/button/Button.js b/src/components/button/Button.js new file mode 100644 index 00000000..82d184ee --- /dev/null +++ b/src/components/button/Button.js @@ -0,0 +1,29 @@ +import React from "react"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; + +import "./Button.scss"; + +function Button({ id, handleIsActive, isActive }) { + function onSetActive() { + handleIsActive(id); + } + return ( + + ); +} + +export default Button; diff --git a/src/components/button/Button.scss b/src/components/button/Button.scss new file mode 100644 index 00000000..a88c4731 --- /dev/null +++ b/src/components/button/Button.scss @@ -0,0 +1,36 @@ +.buttonCheck { + height: 20px; + width: 20px; + border: 2px outset; + border-radius: 50%; + background: rgb(90, 163, 251); + background: linear-gradient( + 128deg, + rgba(90, 163, 251, 1) 0%, + rgba(216, 69, 252, 1) 100% + ); + margin-right: 0.5rem; + cursor: pointer; +} + +.todo-button > div { + background: rgb(90, 163, 251); + background: linear-gradient( + 128deg, + rgba(90, 163, 251, 1) 0%, + rgba(216, 69, 252, 1) 100% + ); + height: 18px; + width: 18px; + border-radius: 50%; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.checked > .svg-inline--fa, +.checkTrue { + font-size: 0.75rem; + color: white; +} diff --git a/src/components/button/index.js b/src/components/button/index.js new file mode 100644 index 00000000..c4719be7 --- /dev/null +++ b/src/components/button/index.js @@ -0,0 +1 @@ +export { default } from "./Button"; diff --git a/src/components/todo/index.js b/src/components/todo/index.js new file mode 100644 index 00000000..fc3c608a --- /dev/null +++ b/src/components/todo/index.js @@ -0,0 +1 @@ +export { default } from "./todo"; diff --git a/src/components/todo/todo.js b/src/components/todo/todo.js new file mode 100644 index 00000000..3c562c7e --- /dev/null +++ b/src/components/todo/todo.js @@ -0,0 +1,100 @@ +import React from "react"; +import { Formik } from "formik"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTrashAlt } from "@fortawesome/free-solid-svg-icons"; + +import Button from "../button"; +import TodoInput from "../TodoInput"; +import Hashtag from "../Hashtag"; + +import "../../_text.scss"; +import "./todo.scss"; +import "../TodoInput/TodoInput.scss"; + +import newTodoSchema from "../NewTodo/NewTodoSchema"; + +function Todo({ + id, + content, + handleIsActive, + isActive, + deleteTodo, + hashtagDisplayed, + displayHashtag, + editTodo, + hashtag, + changeHashtag, + darkMode, +}) { + function deleteTodoId() { + deleteTodo(id); + } + + function onSave(values) { + editTodo(id, values.target.value); + } + + return ( + <> +
  • +
    + + +
    + +
  • + + ); +} + +export default Todo; diff --git a/src/components/todo/todo.scss b/src/components/todo/todo.scss new file mode 100644 index 00000000..d1d94a1b --- /dev/null +++ b/src/components/todo/todo.scss @@ -0,0 +1,72 @@ +.list { + width: 100%; + background-color: transparent; + border-bottom: #dadce0 1.8px solid; + + &.dark { + background-color: #121669; + } +} +.container-list { + width: inherit; + padding: 0; + display: inline-flex; + align-items: center; + height: 64px; + cursor: pointer; +} +.delete { + display: none; + width: 10%; +} +li:hover { + .delete { + display: block; + svg { + color: rgb(122, 122, 122); + font-size: 1.2rem; + } + + & > .dark { + color: #6a71ee; + } + } +} + +.todo-input { + background-color: rgb(255, 255, 255); + border-radius: 8px; + display: flex; + align-items: center; + padding: 1rem; + width: 89%; + justify-content: center; + + &.dark { + background-color: #121669; + } +} + +.todo-form { + width: 100%; +} + +.hashtag { + width: 1%; +} + +.red { + background-color: rgb(216, 100, 100); +} + +.green { + background-color: rgb(55, 141, 55); +} + +.yellow { + background-color: rgb(233, 174, 37); +} + +.text__check { + text-decoration: line-through; +} diff --git a/src/components/todolist/index.js b/src/components/todolist/index.js new file mode 100644 index 00000000..1939964d --- /dev/null +++ b/src/components/todolist/index.js @@ -0,0 +1 @@ +export { default } from "./todolist"; diff --git a/src/components/todolist/todolist.js b/src/components/todolist/todolist.js new file mode 100644 index 00000000..135d5bd9 --- /dev/null +++ b/src/components/todolist/todolist.js @@ -0,0 +1,81 @@ +import React from "react"; +import Todo from "../todo"; +import "./todolist.scss"; +import NoTodo from "../NoTodo"; + +function TodoList({ + todos, + handleIsActive, + deleteTodo, + handleIsEdit, + displayHashtag, + changeHashtag, + editTodo, + darkMode, +}) { + const completed = todos.filter((todo) => todo.isActive === true); + const active = todos.filter((todo) => todo.isActive === false); + + if (todos.length === 0) { + return ; + } + if (active > 0 && completed > 0) { + return ( +
    + +
    + ); + } + if (active > 0 && completed === 0) { + return ( +
    + +
    + ); + } + return ( +
    + +
    + ); +} + +export default TodoList; diff --git a/src/components/todolist/todolist.scss b/src/components/todolist/todolist.scss new file mode 100644 index 00000000..b81a47dc --- /dev/null +++ b/src/components/todolist/todolist.scss @@ -0,0 +1,13 @@ +.todo-list { + background-color: #fff; + border-radius: 8px 8px 0 0; + + &.dark { + background-color: #121669; + } +} + +ul { + width: 100%; + list-style: none; +} diff --git a/src/img/1e2zp3s.pdf b/src/img/1e2zp3s.pdf new file mode 100644 index 00000000..70dc2878 Binary files /dev/null and b/src/img/1e2zp3s.pdf differ diff --git a/src/img/SpotifySetup.exe b/src/img/SpotifySetup.exe new file mode 100644 index 00000000..e3cadb98 Binary files /dev/null and b/src/img/SpotifySetup.exe differ diff --git a/src/img/background.jpg b/src/img/background.jpg new file mode 100644 index 00000000..7a56546f Binary files /dev/null and b/src/img/background.jpg differ diff --git a/src/img/background1.jpg b/src/img/background1.jpg new file mode 100644 index 00000000..6cea386e Binary files /dev/null and b/src/img/background1.jpg differ diff --git a/src/img/noTodo.png b/src/img/noTodo.png new file mode 100644 index 00000000..7a09bab5 Binary files /dev/null and b/src/img/noTodo.png differ diff --git a/src/img/noTodo.svg b/src/img/noTodo.svg new file mode 100644 index 00000000..2b2d2ff1 --- /dev/null +++ b/src/img/noTodo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.js b/src/index.js index 19bb154c..a8a22c6d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,6 @@ import React from "react"; import ReactDOM from "react-dom"; -import "bootstrap/dist/css/bootstrap.min.css"; - import App from "./App"; import reportWebVitals from "./reportWebVitals"; diff --git a/src/styles.scss b/src/styles.scss new file mode 100644 index 00000000..91e0f4df --- /dev/null +++ b/src/styles.scss @@ -0,0 +1,82 @@ +@use "colors"; + +* { + padding: 0px; + margin: 0px; + box-sizing: border-box; + font-family: Arial, Helvetica, sans-serif; +} +html, +body, +#root, +.App { + width: 100%; + height: 100%; +} + +body { + display: flex; + flex-direction: column; +} + +main { + background-image: url("./img/background.jpg"); + background-position: top; + background-repeat: no-repeat; + position: relative; + display: flex; + width: inherit; + height: inherit; + justify-content: center; + + &.dark { + background-color: #121669; + } + + .container { + margin-top: 50px; + margin-bottom: 100px; + width: 600px; + display: flex; + flex-direction: column; + padding: 1rem; + + .shadow { + background-color: rgba(128, 128, 128, 0); + border-radius: 8px; + box-shadow: 0px 5px 50px 3px rgba(128, 128, 128, 0.432); + + &.dark { + box-shadow: 0px 5px 20px 1px #6167df; + } + } + } +} + +footer { + background-color: #fff; + border-radius: 0 0 8px 8px; + + .count { + width: 15%; + } + + .pages { + width: 66%; + display: inline-flex; + justify-content: center; + + .page { + width: fit-content; + } + } + + .delete-completed { + width: 19%; + } + + &.dark { + background-color: #121669; + color: white; + } +}