-
Notifications
You must be signed in to change notification settings - Fork 46
Solution: Jonathan Cedeño & Alfonso García #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
52dcc2e
a9f032d
d02f1c9
5cffae0
b823cdb
b5c4251
352a8f6
105f7ac
4ceed55
0fd154b
0d81d12
cb31525
cc6dd1b
7ca9284
0d5d32d
d1eabe8
4232191
c62865d
58dc33b
5197d52
e6eb9e5
2278da0
faef38e
aef14c1
129d2d1
af5428e
1c3718a
b485eee
44035ed
fddec88
a8bc7f3
0f1eb59
4d84df2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,4 +121,5 @@ dist | |
**/*.backup.* | ||
**/*.back.* | ||
|
||
node_modules | ||
node_modules | ||
package-lock.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,5 +15,5 @@ | |
"tabWidth": 2, | ||
"trailingComma": "all", | ||
"useTabs": false, | ||
"endOfLine": "lf" | ||
"endOfLine": "auto" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,220 @@ | ||
import React from "react"; | ||
|
||
function App() { | ||
return ( | ||
<main className="container mt-5"> | ||
<section className="row"> | ||
<div className="col col-12"> | ||
<h1>Hola mundo</h1> | ||
import React, { Component } from "react"; | ||
import { BrowserRouter, Route } from "react-router-dom"; | ||
|
||
import Home from "./pages/Home"; | ||
import Completed from "./pages/Completed"; | ||
import Active from "./pages/Active"; | ||
|
||
import Main from "./components/Main"; | ||
import Header from "./components/Header"; | ||
import NewTask from "./components/NewTask"; | ||
import ListFooter from "./components/ListFooter"; | ||
|
||
import * as api from "./api"; | ||
|
||
const LOCAL_STORAGE_KEY = "react-tasks-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 = { | ||
tasks: [], | ||
isDark: false, | ||
}; | ||
|
||
this.saveNewTask = this.saveNewTask.bind(this); | ||
this.toggleDarkLightMode = this.toggleDarkLightMode.bind(this); | ||
this.handleDeleteTask = this.handleDeleteTask.bind(this); | ||
this.handleClearCompleted = this.handleClearCompleted.bind(this); | ||
this.handleUpdateTask = this.handleUpdateTask.bind(this); | ||
this.handleToggleEditing = this.handleToggleEditing.bind(this); | ||
this.handleToggleCheck = this.handleToggleCheck.bind(this); | ||
} | ||
|
||
componentDidMount() { | ||
const prevItems = loadLocalStorageData(); | ||
|
||
if (!prevItems) { | ||
api.getTasks().then((data) => { | ||
this.setState({ | ||
tasks: data, | ||
}); | ||
}); | ||
return; | ||
} | ||
|
||
this.setState({ | ||
tasks: prevItems.tasks, | ||
}); | ||
} | ||
|
||
componentDidUpdate() { | ||
const { tasks } = this.state; | ||
|
||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({ tasks })); | ||
} | ||
|
||
handleDeleteTask(event, taskId) { | ||
const { tasks } = this.state; | ||
|
||
const updatedTasks = tasks.filter((task) => task.id !== taskId); | ||
|
||
this.setState({ tasks: updatedTasks }); | ||
} | ||
|
||
handleClearCompleted() { | ||
const { tasks } = this.state; | ||
|
||
const updatedTasks = tasks.filter((task) => !task.isCompleted); | ||
|
||
this.setState({ tasks: updatedTasks }); | ||
} | ||
Comment on lines
+72
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Muy limpio y bien implementado 👏🏻👏🏻 |
||
|
||
handleToggleCheck(taskId) { | ||
const { tasks } = this.state; | ||
|
||
const updatedTasks = tasks.map((task) => { | ||
if (task.id === taskId) { | ||
return { | ||
...task, | ||
isCompleted: !task.isCompleted, | ||
}; | ||
} | ||
return task; | ||
}); | ||
|
||
this.setState({ tasks: updatedTasks }); | ||
} | ||
|
||
handleToggleEditing(taskId) { | ||
const { tasks } = this.state; | ||
|
||
const updatedTasks = tasks.map((task) => { | ||
if (task.id === taskId) { | ||
return { | ||
...task, | ||
isEditing: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Muy interesante la solución para indicar que el todo está siendo editado actualmente. |
||
}; | ||
} | ||
|
||
return task; | ||
}); | ||
|
||
this.setState({ tasks: updatedTasks }); | ||
} | ||
|
||
handleUpdateTask(data, taskId) { | ||
const { tasks } = this.state; | ||
|
||
const updatedTasks = tasks.map((task) => { | ||
if (task.id === taskId && data.title !== task.title) { | ||
return { | ||
...task, | ||
title: data.title, | ||
isEditing: false, | ||
}; | ||
} | ||
return { | ||
...task, | ||
isEditing: false, | ||
}; | ||
}); | ||
|
||
this.setState({ tasks: updatedTasks }); | ||
} | ||
|
||
saveNewTask(newTask) { | ||
this.setState((prevState) => ({ | ||
tasks: [newTask, ...prevState.tasks], | ||
})); | ||
} | ||
|
||
toggleDarkLightMode() { | ||
const { isDark } = this.state; | ||
if (isDark === true) this.setState({ isDark: false }); | ||
else this.setState({ isDark: true }); | ||
} | ||
Comment on lines
+147
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Muy bien implementado! Como mejora podríais usar el patrón de functional updates: this.setState((prevState) => ({
isDark: !prevState.isDark,
})); |
||
|
||
render() { | ||
const { tasks, isDark } = this.state; | ||
|
||
return ( | ||
<Main> | ||
<div className="list-header"> | ||
<Header | ||
toggleDarkLightMode={this.toggleDarkLightMode} | ||
isDark={isDark} | ||
/> | ||
<NewTask saveNewTask={this.saveNewTask} /> | ||
</div> | ||
<div className="list-container"> | ||
<BrowserRouter> | ||
<Route | ||
path="/" | ||
exact | ||
render={(routeProps) => ( | ||
<Home | ||
{...routeProps} | ||
tasks={tasks} | ||
handleDeleteTask={this.handleDeleteTask} | ||
handleUpdateTask={this.handleUpdateTask} | ||
handleToggleEditing={this.handleToggleEditing} | ||
handleToggleCheck={this.handleToggleCheck} | ||
/> | ||
)} | ||
/> | ||
<Route | ||
path="/completed" | ||
exact | ||
render={(routeProps) => ( | ||
<Completed | ||
{...routeProps} | ||
tasks={tasks} | ||
handleDeleteTask={this.handleDeleteTask} | ||
handleUpdateTask={this.handleUpdateTask} | ||
handleToggleEditing={this.handleToggleEditing} | ||
handleToggleCheck={this.handleToggleCheck} | ||
/> | ||
)} | ||
/> | ||
<Route | ||
path="/active" | ||
exact | ||
render={(routeProps) => ( | ||
<Active | ||
{...routeProps} | ||
tasks={tasks} | ||
handleDeleteTask={this.handleDeleteTask} | ||
handleUpdateTask={this.handleUpdateTask} | ||
handleToggleEditing={this.handleToggleEditing} | ||
handleToggleCheck={this.handleToggleCheck} | ||
/> | ||
)} | ||
/> | ||
<ListFooter | ||
handleClearCompleted={this.handleClearCompleted} | ||
tasksLeft={tasks.filter((task) => !task.isCompleted).length} | ||
/> | ||
</BrowserRouter> | ||
</div> | ||
</section> | ||
</main> | ||
); | ||
</Main> | ||
); | ||
} | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import tasks from "../utils/demo-data"; | ||
|
||
function getTasks(fail = false) { | ||
return new Promise((res, rej) => { | ||
setTimeout(() => { | ||
if (fail) { | ||
rej(new Error("Failed to fetch")); | ||
} | ||
|
||
res(tasks); | ||
}, 1000); | ||
}); | ||
} | ||
|
||
export { getTasks }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./getTasks"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React, { Component } from "react"; | ||
|
||
import "./Checkbox.scss"; | ||
|
||
class Checkbox extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
isChecked: props.defaultChecked, | ||
taskId: props.taskId, | ||
}; | ||
Comment on lines
+8
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No es recomendable inicializar el state basandose props ya que puede generar muchos bugs. Una solución más sencilla es pasar los props de <input
type="checkbox"
onChange={this.toggleChange}
/> |
||
this.toggleChange = this.toggleChange.bind(this); | ||
this.handleToggleCheck = props.handleToggleCheck; | ||
} | ||
|
||
toggleChange() { | ||
this.setState((prevState) => ({ | ||
isChecked: !prevState.isChecked, | ||
})); | ||
const { taskId } = this.state; | ||
this.handleToggleCheck(taskId); | ||
} | ||
|
||
render() { | ||
const { taskId, isChecked } = this.state; | ||
return ( | ||
<div className="round-checkbox"> | ||
<input | ||
id={taskId} | ||
type="checkbox" | ||
defaultChecked={isChecked} | ||
onChange={this.toggleChange} | ||
/> | ||
{/* eslint-disable-next-line */} | ||
<label htmlFor={taskId} /> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default Checkbox; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
.round-checkbox { | ||
position: relative; | ||
& input[type="checkbox"]:not(:checked) + label:hover { | ||
border: 4px solid; | ||
border-color: rgb(66, 126, 179); | ||
} | ||
& input[type="checkbox"]:checked + label:hover { | ||
border: 1px solid; | ||
border-color: rgb(20, 71, 116); | ||
} | ||
& label { | ||
background-color: #fff; | ||
border: 1px solid #ccc; | ||
border-radius: 50%; | ||
cursor: pointer; | ||
height: 28px; | ||
left: 0; | ||
position: absolute; | ||
top: 0; | ||
width: 28px; | ||
&:after { | ||
border: 2px solid #fff; | ||
border-top: none; | ||
border-right: none; | ||
content: ""; | ||
height: 6px; | ||
left: 7px; | ||
opacity: 0; | ||
position: absolute; | ||
top: 8px; | ||
transform: rotate(-45deg); | ||
width: 12px; | ||
} | ||
} | ||
& input[type="checkbox"] { | ||
visibility: hidden; | ||
} | ||
& input[type="checkbox"]:checked + label { | ||
background: linear-gradient( | ||
-45deg, | ||
rgb(149, 184, 214), | ||
rgb(66, 126, 179), | ||
white | ||
); | ||
} | ||
& input[type="checkbox"]:checked + label:after { | ||
opacity: 1; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./Checkbox"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Muy buen uso de la propiedad de estado para indicar el dark mode! Como mejora se podría usar un nombre más descriptivo como
isDarkModeActive
.