Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run pre:commit
4 changes: 4 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run pre:push
8 changes: 8 additions & 0 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
"**/*.js": [
"npm run lint:js",
"npm run lint:format:check",
"npm run test:related",
],
"*.{css,scss,html,md,json,yml,yaml}": ["npm run lint:format:check"],
};
10 changes: 10 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link
rel="stylesheet"
href="https://unicons.iconscout.com/release/v3.0.6/css/line.css"
/>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@100;300;400;700&display=swap"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
Expand Down
217 changes: 205 additions & 12 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,208 @@
import React from "react";

function App() {
return (
<main className="container mt-5">
<section className="row">
<div className="col col-12">
<h1>Hola mundo</h1>
</div>
</section>
</main>
);
import React, { Component } from "react";
import { Route } from "react-router-dom";
import { v4 as uuid } from "uuid";

import { LOCAL_STORAGE_KEY } from "./utils/contants";
import {
getPreviousLocalStorageValues,
setLocalStorageValues,
} from "./utils/methods";

import TodoList from "./components/TodoList";

class App extends Component {
constructor(props) {
super(props);

this.state = {
todos: [],
darkMode: false,
};

this.handleAddTodo = this.handleAddTodo.bind(this);
this.handleMarkTodoAsDone = this.handleMarkTodoAsDone.bind(this);
this.handleDeleteTodo = this.handleDeleteTodo.bind(this);
this.handleEditTodo = this.handleEditTodo.bind(this);
this.handleThemeChange = this.handleThemeChange.bind(this);
this.handleClearCompletedTodos = this.handleClearCompletedTodos.bind(this);
this.handleIsEditingTodo = this.handleIsEditingTodo.bind(this);
}

componentDidMount() {
const previousTodos = getPreviousLocalStorageValues(LOCAL_STORAGE_KEY);

if (Array.isArray(previousTodos) && previousTodos.length > 0) {
this.setState({
todos: previousTodos,
});
}
}

componentDidUpdate() {
const { todos } = this.state;
setLocalStorageValues(LOCAL_STORAGE_KEY, todos);
}

handleMarkTodoAsDone(todoId) {
const { todos } = this.state;

const updatedTodos = todos.map((todo) => {
if (todo.id === todoId) {
return {
...todo,
done: !todo.done,
isEditing: false,
};
}
return todo;
});

this.setState({ todos: updatedTodos });
}

handleDeleteTodo(todoId) {
const { todos } = this.state;
const filteredTodos = todos.filter((todo) => todo.id !== todoId);
this.setState({ todos: filteredTodos });
}

handleAddTodo(text) {
this.setState((prevState) => ({
todos: [
...prevState.todos,
{
id: uuid(),
text: text,
done: false,
isEditing: false,
},
],
}));
}

handleEditTodo(todoId, editedText) {
const { todos } = this.state;

const updatedTodos = todos.map((todo) => {
if (todo.id === todoId) {
return {
...todo,
text: editedText,
isEditing: false,
};
}

return todo;
});

this.setState({ todos: updatedTodos });
}

handleThemeChange() {
this.setState((prevState) => ({
darkMode: !prevState.darkMode,
}));
}

handleClearCompletedTodos() {
const { todos } = this.state;
const updatedTodos = todos.filter((todo) => !todo.done);
this.setState({ todos: updatedTodos });
}

handleIsEditingTodo(todoId) {
const { todos } = this.state;

const mappedTodos = todos.map((todo) => {
if (todo.id === todoId) {
return {
...todo,
isEditing: true,
};
}

return {
...todo,
isEditing: false,
};
});

this.setState({
todos: mappedTodos,
});
}

render() {
const { todos, darkMode } = this.state;
const activeTodos = todos.filter((todo) => !todo.done);
const completedTodos = todos.filter((todo) => todo.done);
const activeTodosCount = todos.filter((todo) => !todo.done).length;

return (
<>
<Route
path="/"
exact
render={(routeProps) => (
<TodoList
{...routeProps}
todos={todos}
todosLeft={activeTodosCount}
hasTodos={todos.length}
darkMode={darkMode}
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
handleDeleteTodo={this.handleDeleteTodo}
handleEditTodo={this.handleEditTodo}
handleClearCompletedTodos={this.handleClearCompletedTodos}
handleIsEditingTodo={this.handleIsEditingTodo}
handleThemeChange={this.handleThemeChange}
handleAddTodo={this.handleAddTodo}
/>
)}
/>
<Route
path="/active"
exact
render={(routeProps) => (
<TodoList
{...routeProps}
todos={activeTodos}
todosLeft={activeTodosCount}
hasTodos={todos.length}
darkMode={darkMode}
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
handleDeleteTodo={this.handleDeleteTodo}
handleEditTodo={this.handleEditTodo}
handleClearCompletedTodos={this.handleClearCompletedTodos}
handleIsEditingTodo={this.handleIsEditingTodo}
handleThemeChange={this.handleThemeChange}
handleAddTodo={this.handleAddTodo}
/>
)}
/>
<Route
path="/completed"
exact
render={(routeProps) => (
<TodoList
{...routeProps}
todos={completedTodos}
todosLeft={activeTodosCount}
hasTodos={todos.length}
darkMode={darkMode}
handleMarkTodoAsDone={this.handleMarkTodoAsDone}
handleDeleteTodo={this.handleDeleteTodo}
handleEditTodo={this.handleEditTodo}
handleClearCompletedTodos={this.handleClearCompletedTodos}
handleIsEditingTodo={this.handleIsEditingTodo}
handleThemeChange={this.handleThemeChange}
handleAddTodo={this.handleAddTodo}
/>
)}
/>
</>
);
}
}

export default App;
83 changes: 83 additions & 0 deletions src/components/AppFooter/AppFooter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from "react";
import { NavLink } from "react-router-dom";
import classNames from "classnames";

import "./AppFooter.scss";

function AppFooter({ todosLeft, darkMode, handleClearCompletedTodos }) {
const darkModeTodosLeft = classNames({
"items-left footer-item": true,
"footer-item-dark": darkMode,
});

const darkModeClear = classNames({
"clear-all-div footer-item clear-button": true,
"footer-item-dark": darkMode,
});

const darkModeNavLink = classNames({
"nav-link": true,
"nav-link-dark": darkMode,
});

const darkModeActive = classNames({
active: true,
"active-dark": darkMode,
});

return (
<footer
className="app-footer col col-12 py-3 px-4"
data-testid="app-footer"
>
<p className={darkModeTodosLeft}>{todosLeft} todos left</p>
<nav className="footer-item">
<ul className="navbar-nav d-flex">
<li className="nav-item">
<NavLink
exact
activeClassName={darkModeActive}
className={darkModeNavLink}
data-testid="footer-home-link"
to="/"
>
All
</NavLink>
</li>
<li className="nav-item">
<NavLink
exact
activeClassName={darkModeActive}
className={darkModeNavLink}
data-testid="footer-active-link"
to="/active"
>
Active
</NavLink>
</li>
<li className="nav-item">
<NavLink
exact
activeClassName={darkModeActive}
className={darkModeNavLink}
data-testid="footer-completed-link"
to="/completed"
>
Completed
</NavLink>
</li>
</ul>
</nav>
<button
type="button"
onClick={handleClearCompletedTodos}
className={darkModeClear}
data-testid="clear-completed-todos"
>
Clear completed
</button>
</footer>
);
}

export default AppFooter;
Loading