Skip to content

Commit dfee92f

Browse files
committed
feat: add improved solution: ricard garcia & victor hugo gomez
From Pull Request #6
1 parent 3df7625 commit dfee92f

30 files changed

+1238
-13
lines changed

.husky/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_

.husky/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npm run pre:commit

.husky/pre-push

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npm run pre:push

.lintstagedrc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
"**/*.js": [
3+
"npm run lint:js",
4+
"npm run lint:format:check",
5+
"npm run test:related",
6+
],
7+
"*.{css,scss,html,md,json,yml,yaml}": ["npm run lint:format:check"],
8+
};

public/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
<head>
44
<meta charset="utf-8" />
55
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6+
<link
7+
rel="stylesheet"
8+
href="https://unicons.iconscout.com/release/v3.0.6/css/line.css"
9+
/>
10+
<link rel="preconnect" href="https://fonts.gstatic.com" />
11+
<link rel="preconnect" href="https://fonts.gstatic.com" />
12+
<link
13+
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@100;300;400;700&display=swap"
14+
rel="stylesheet"
15+
/>
616
<meta name="viewport" content="width=device-width, initial-scale=1" />
717
<meta name="theme-color" content="#000000" />
818
<meta

src/App.js

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,208 @@
1-
import React from "react";
2-
3-
function App() {
4-
return (
5-
<main className="container mt-5">
6-
<section className="row">
7-
<div className="col col-12">
8-
<h1>Hola mundo</h1>
9-
</div>
10-
</section>
11-
</main>
12-
);
1+
import React, { Component } from "react";
2+
import { Route } from "react-router-dom";
3+
import { v4 as uuid } from "uuid";
4+
5+
import { LOCAL_STORAGE_KEY } from "./utils/contants";
6+
import {
7+
getPreviousLocalStorageValues,
8+
setLocalStorageValues,
9+
} from "./utils/methods";
10+
11+
import TodoList from "./components/TodoList";
12+
13+
class App extends Component {
14+
constructor(props) {
15+
super(props);
16+
17+
this.state = {
18+
todos: [],
19+
darkMode: false,
20+
};
21+
22+
this.handleAddTodo = this.handleAddTodo.bind(this);
23+
this.handleMarkTodoAsDone = this.handleMarkTodoAsDone.bind(this);
24+
this.handleDeleteTodo = this.handleDeleteTodo.bind(this);
25+
this.handleEditTodo = this.handleEditTodo.bind(this);
26+
this.handleThemeChange = this.handleThemeChange.bind(this);
27+
this.handleClearCompletedTodos = this.handleClearCompletedTodos.bind(this);
28+
this.handleIsEditingTodo = this.handleIsEditingTodo.bind(this);
29+
}
30+
31+
componentDidMount() {
32+
const previousTodos = getPreviousLocalStorageValues(LOCAL_STORAGE_KEY);
33+
34+
if (Array.isArray(previousTodos) && previousTodos.length > 0) {
35+
this.setState({
36+
todos: previousTodos,
37+
});
38+
}
39+
}
40+
41+
componentDidUpdate() {
42+
const { todos } = this.state;
43+
setLocalStorageValues(LOCAL_STORAGE_KEY, todos);
44+
}
45+
46+
handleMarkTodoAsDone(todoId) {
47+
const { todos } = this.state;
48+
49+
const updatedTodos = todos.map((todo) => {
50+
if (todo.id === todoId) {
51+
return {
52+
...todo,
53+
done: !todo.done,
54+
isEditing: false,
55+
};
56+
}
57+
return todo;
58+
});
59+
60+
this.setState({ todos: updatedTodos });
61+
}
62+
63+
handleDeleteTodo(todoId) {
64+
const { todos } = this.state;
65+
const filteredTodos = todos.filter((todo) => todo.id !== todoId);
66+
this.setState({ todos: filteredTodos });
67+
}
68+
69+
handleAddTodo(text) {
70+
this.setState((prevState) => ({
71+
todos: [
72+
...prevState.todos,
73+
{
74+
id: uuid(),
75+
text: text,
76+
done: false,
77+
isEditing: false,
78+
},
79+
],
80+
}));
81+
}
82+
83+
handleEditTodo(todoId, editedText) {
84+
const { todos } = this.state;
85+
86+
const updatedTodos = todos.map((todo) => {
87+
if (todo.id === todoId) {
88+
return {
89+
...todo,
90+
text: editedText,
91+
isEditing: false,
92+
};
93+
}
94+
95+
return todo;
96+
});
97+
98+
this.setState({ todos: updatedTodos });
99+
}
100+
101+
handleThemeChange() {
102+
this.setState((prevState) => ({
103+
darkMode: !prevState.darkMode,
104+
}));
105+
}
106+
107+
handleClearCompletedTodos() {
108+
const { todos } = this.state;
109+
const updatedTodos = todos.filter((todo) => !todo.done);
110+
this.setState({ todos: updatedTodos });
111+
}
112+
113+
handleIsEditingTodo(todoId) {
114+
const { todos } = this.state;
115+
116+
const mappedTodos = todos.map((todo) => {
117+
if (todo.id === todoId) {
118+
return {
119+
...todo,
120+
isEditing: true,
121+
};
122+
}
123+
124+
return {
125+
...todo,
126+
isEditing: false,
127+
};
128+
});
129+
130+
this.setState({
131+
todos: mappedTodos,
132+
});
133+
}
134+
135+
render() {
136+
const { todos, darkMode } = this.state;
137+
const activeTodos = todos.filter((todo) => !todo.done);
138+
const completedTodos = todos.filter((todo) => todo.done);
139+
const activeTodosCount = todos.filter((todo) => !todo.done).length;
140+
141+
return (
142+
<>
143+
<Route
144+
path="/"
145+
exact
146+
render={(routeProps) => (
147+
<TodoList
148+
{...routeProps}
149+
todos={todos}
150+
todosLeft={activeTodosCount}
151+
hasTodos={todos.length}
152+
darkMode={darkMode}
153+
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
154+
handleDeleteTodo={this.handleDeleteTodo}
155+
handleEditTodo={this.handleEditTodo}
156+
handleClearCompletedTodos={this.handleClearCompletedTodos}
157+
handleIsEditingTodo={this.handleIsEditingTodo}
158+
handleThemeChange={this.handleThemeChange}
159+
handleAddTodo={this.handleAddTodo}
160+
/>
161+
)}
162+
/>
163+
<Route
164+
path="/active"
165+
exact
166+
render={(routeProps) => (
167+
<TodoList
168+
{...routeProps}
169+
todos={activeTodos}
170+
todosLeft={activeTodosCount}
171+
hasTodos={todos.length}
172+
darkMode={darkMode}
173+
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
174+
handleDeleteTodo={this.handleDeleteTodo}
175+
handleEditTodo={this.handleEditTodo}
176+
handleClearCompletedTodos={this.handleClearCompletedTodos}
177+
handleIsEditingTodo={this.handleIsEditingTodo}
178+
handleThemeChange={this.handleThemeChange}
179+
handleAddTodo={this.handleAddTodo}
180+
/>
181+
)}
182+
/>
183+
<Route
184+
path="/completed"
185+
exact
186+
render={(routeProps) => (
187+
<TodoList
188+
{...routeProps}
189+
todos={completedTodos}
190+
todosLeft={activeTodosCount}
191+
hasTodos={todos.length}
192+
darkMode={darkMode}
193+
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
194+
handleDeleteTodo={this.handleDeleteTodo}
195+
handleEditTodo={this.handleEditTodo}
196+
handleClearCompletedTodos={this.handleClearCompletedTodos}
197+
handleIsEditingTodo={this.handleIsEditingTodo}
198+
handleThemeChange={this.handleThemeChange}
199+
handleAddTodo={this.handleAddTodo}
200+
/>
201+
)}
202+
/>
203+
</>
204+
);
205+
}
13206
}
14207

15208
export default App;

src/components/AppFooter/AppFooter.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React from "react";
2+
import { NavLink } from "react-router-dom";
3+
import classNames from "classnames";
4+
5+
import "./AppFooter.scss";
6+
7+
function AppFooter({ todosLeft, darkMode, handleClearCompletedTodos }) {
8+
const darkModeTodosLeft = classNames({
9+
"items-left footer-item": true,
10+
"footer-item-dark": darkMode,
11+
});
12+
13+
const darkModeClear = classNames({
14+
"clear-all-div footer-item clear-button": true,
15+
"footer-item-dark": darkMode,
16+
});
17+
18+
const darkModeNavLink = classNames({
19+
"nav-link": true,
20+
"nav-link-dark": darkMode,
21+
});
22+
23+
const darkModeActive = classNames({
24+
active: true,
25+
"active-dark": darkMode,
26+
});
27+
28+
return (
29+
<footer
30+
className="app-footer col col-12 py-3 px-4"
31+
data-testid="app-footer"
32+
>
33+
<p className={darkModeTodosLeft}>{todosLeft} todos left</p>
34+
<nav className="footer-item">
35+
<ul className="navbar-nav d-flex">
36+
<li className="nav-item">
37+
<NavLink
38+
exact
39+
activeClassName={darkModeActive}
40+
className={darkModeNavLink}
41+
data-testid="footer-home-link"
42+
to="/"
43+
>
44+
All
45+
</NavLink>
46+
</li>
47+
<li className="nav-item">
48+
<NavLink
49+
exact
50+
activeClassName={darkModeActive}
51+
className={darkModeNavLink}
52+
data-testid="footer-active-link"
53+
to="/active"
54+
>
55+
Active
56+
</NavLink>
57+
</li>
58+
<li className="nav-item">
59+
<NavLink
60+
exact
61+
activeClassName={darkModeActive}
62+
className={darkModeNavLink}
63+
data-testid="footer-completed-link"
64+
to="/completed"
65+
>
66+
Completed
67+
</NavLink>
68+
</li>
69+
</ul>
70+
</nav>
71+
<button
72+
type="button"
73+
onClick={handleClearCompletedTodos}
74+
className={darkModeClear}
75+
data-testid="clear-completed-todos"
76+
>
77+
Clear completed
78+
</button>
79+
</footer>
80+
);
81+
}
82+
83+
export default AppFooter;

0 commit comments

Comments
 (0)