-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
implementation #2160
base: master
Are you sure you want to change the base?
implementation #2160
Changes from 1 commit
d5c0153
c3d8359
b587817
bfc0eb9
344ae2b
0f4f759
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 | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,61 +1,120 @@ | ||||||||||||||||||||||||||
import './App.scss'; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// import usersFromServer from './api/users'; | ||||||||||||||||||||||||||
// import todosFromServer from './api/todos'; | ||||||||||||||||||||||||||
import { useState } from 'react'; | ||||||||||||||||||||||||||
import usersFromServer from './api/users'; | ||||||||||||||||||||||||||
import todosFromServer from './api/todos'; | ||||||||||||||||||||||||||
import { TodoList } from './components/TodoList'; | ||||||||||||||||||||||||||
import { User } from './types/user'; | ||||||||||||||||||||||||||
import { findUserById } from './services/findUserById'; | ||||||||||||||||||||||||||
import { getNextTodoId } from './services/getNextTodoId'; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export const App = () => { | ||||||||||||||||||||||||||
const [emptyTitleFieldError, setEmptyTitleFieldError] = useState(false); | ||||||||||||||||||||||||||
const [emptyUserFieldError, setEmptyUserFieldError] = useState(false); | ||||||||||||||||||||||||||
const [todosList, setTodosList] = useState(todosFromServer); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const [formData, setFormData] | ||||||||||||||||||||||||||
= useState<{ title: string, user: User | null }>({ title: '', user: null }); | ||||||||||||||||||||||||||
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. better to create an interface for it: interface FormFileds {
title: string;
user: User | null;
} 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.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { | ||||||||||||||||||||||||||
event.preventDefault(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
setEmptyTitleFieldError(!formData.title.trim()); | ||||||||||||||||||||||||||
setEmptyUserFieldError(!formData.user); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (!formData.title || !formData.user) { | ||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
setTodosList([...todosList, { | ||||||||||||||||||||||||||
id: getNextTodoId(todosList), | ||||||||||||||||||||||||||
title: formData.title.trim(), | ||||||||||||||||||||||||||
completed: false, | ||||||||||||||||||||||||||
userId: formData.user.id, | ||||||||||||||||||||||||||
}]); | ||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const form = event.target as HTMLFormElement; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
form.reset(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
setFormData(({ title: '', user: null })); | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const keyCheck = (event: React.KeyboardEvent) => { | ||||||||||||||||||||||||||
if (!/^[A-Za-zА-Яа-яЁёіїєґҐґ ]*$/ | ||||||||||||||||||||||||||
.test(event.key)) { | ||||||||||||||||||||||||||
event.preventDefault(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const handleUserChange = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||||||||||||||||||||||||||
setFormData({ | ||||||||||||||||||||||||||
title: formData.title, | ||||||||||||||||||||||||||
user: findUserById(+event.currentTarget.value), | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
setEmptyUserFieldError(false); | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const handleTitleChange = (event: React.FormEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||
setFormData({ | ||||||||||||||||||||||||||
title: event.currentTarget.value, | ||||||||||||||||||||||||||
user: formData.user, | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
setEmptyTitleFieldError(false); | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||
<div className="App"> | ||||||||||||||||||||||||||
<h1>Add todo form</h1> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<form action="/api/todos" method="POST"> | ||||||||||||||||||||||||||
<form | ||||||||||||||||||||||||||
action="/api/todos" | ||||||||||||||||||||||||||
method="POST" | ||||||||||||||||||||||||||
onSubmit={event => handleFormSubmit(event)} | ||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||
<div className="field"> | ||||||||||||||||||||||||||
<input type="text" data-cy="titleInput" /> | ||||||||||||||||||||||||||
<span className="error">Please enter a title</span> | ||||||||||||||||||||||||||
<label htmlFor="title">Title: </label> | ||||||||||||||||||||||||||
<input | ||||||||||||||||||||||||||
placeholder="Enter a title" | ||||||||||||||||||||||||||
id="title" | ||||||||||||||||||||||||||
onInput={event => handleTitleChange(event)} | ||||||||||||||||||||||||||
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.
Suggested change
we can simplify it 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. fix it everywhere |
||||||||||||||||||||||||||
type="text" | ||||||||||||||||||||||||||
data-cy="titleInput" | ||||||||||||||||||||||||||
onKeyDown={keyCheck} | ||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||
{emptyTitleFieldError | ||||||||||||||||||||||||||
&& (<span className="error">Please enter a title</span>)} | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<div className="field"> | ||||||||||||||||||||||||||
<select data-cy="userSelect"> | ||||||||||||||||||||||||||
<label htmlFor="user">User: </label> | ||||||||||||||||||||||||||
<select | ||||||||||||||||||||||||||
id="user" | ||||||||||||||||||||||||||
data-cy="userSelect" | ||||||||||||||||||||||||||
defaultValue={0} | ||||||||||||||||||||||||||
onChange={event => handleUserChange(event)} | ||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||
<option value="0" disabled>Choose a user</option> | ||||||||||||||||||||||||||
{usersFromServer.map(user => ( | ||||||||||||||||||||||||||
<option | ||||||||||||||||||||||||||
value={user.id} | ||||||||||||||||||||||||||
key={user.id} | ||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||
{user.name} | ||||||||||||||||||||||||||
</option> | ||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||
</select> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<span className="error">Please choose a user</span> | ||||||||||||||||||||||||||
{emptyUserFieldError | ||||||||||||||||||||||||||
&& (<span className="error">Please choose a user</span>)} | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<button type="submit" data-cy="submitButton"> | ||||||||||||||||||||||||||
Add | ||||||||||||||||||||||||||
</button> | ||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<section className="TodoList"> | ||||||||||||||||||||||||||
<article data-id="1" className="TodoInfo TodoInfo--completed"> | ||||||||||||||||||||||||||
<h2 className="TodoInfo__title"> | ||||||||||||||||||||||||||
delectus aut autem | ||||||||||||||||||||||||||
</h2> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<a className="UserInfo" href="mailto:Sincere@april.biz"> | ||||||||||||||||||||||||||
Leanne Graham | ||||||||||||||||||||||||||
</a> | ||||||||||||||||||||||||||
</article> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<article data-id="15" className="TodoInfo TodoInfo--completed"> | ||||||||||||||||||||||||||
<h2 className="TodoInfo__title">delectus aut autem</h2> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<a className="UserInfo" href="mailto:Sincere@april.biz"> | ||||||||||||||||||||||||||
Leanne Graham | ||||||||||||||||||||||||||
</a> | ||||||||||||||||||||||||||
</article> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<article data-id="2" className="TodoInfo"> | ||||||||||||||||||||||||||
<h2 className="TodoInfo__title"> | ||||||||||||||||||||||||||
quis ut nam facilis et officia qui | ||||||||||||||||||||||||||
</h2> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
<a className="UserInfo" href="mailto:Julianne.OConner@kory.org"> | ||||||||||||||||||||||||||
Patricia Lebsack | ||||||||||||||||||||||||||
</a> | ||||||||||||||||||||||||||
</article> | ||||||||||||||||||||||||||
</section> | ||||||||||||||||||||||||||
<TodoList todos={todosList} /> | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,24 @@ | ||
export const TodoInfo = () => {}; | ||
import classNames from 'classnames'; | ||
import { findUserById } from '../../services/findUserById'; | ||
import { Todo } from '../../types/todo'; | ||
import { UserInfo } from '../UserInfo'; | ||
|
||
type Props = { | ||
todo: Todo; | ||
}; | ||
|
||
export const TodoInfo: React.FC<Props> = ({ todo }) => { | ||
return ( | ||
<article | ||
data-id={todo.id} | ||
className={classNames('TodoInfo', { | ||
'TodoInfo--completed': todo.completed, | ||
})} | ||
> | ||
<h2 className="TodoInfo__title"> | ||
{todo.title} | ||
</h2> | ||
<UserInfo user={findUserById(todo.userId)} /> | ||
</article> | ||
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. user can be null, you need to handle this case here 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. I decide to move this check using 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. @Daniilart01 but you don't know where else this component can be reused so need to check if user exist |
||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,19 @@ | ||
export const TodoList = () => {}; | ||
import { Todo } from '../../types/todo'; | ||
import { TodoInfo } from '../TodoInfo'; | ||
|
||
type Props = { | ||
todos: Todo[] | ||
}; | ||
|
||
export const TodoList: React.FC<Props> = ({ todos }) => { | ||
return ( | ||
<section className="TodoList"> | ||
{todos.map(todo => ( | ||
<TodoInfo | ||
todo={todo} | ||
key={todo.id} | ||
/> | ||
))} | ||
</section> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,13 @@ | ||
export const UserInfo = () => {}; | ||
import { User } from '../../types/user'; | ||
|
||
type Props = { | ||
user: User | ||
}; | ||
|
||
export const UserInfo: React.FC<Props> = ({ user }) => { | ||
return ( | ||
<a className="UserInfo" href={`mailto:${user.email}`}> | ||
{user.name} | ||
</a> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,6 @@ | ||||||
import usersFromServer from '../api/users'; | ||||||
import { User } from '../types/user'; | ||||||
|
||||||
export const findUserById = (id: number): User => ( | ||||||
usersFromServer.find(user => user.id === id) as User | ||||||
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. find can return the undefined
Suggested change
|
||||||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Todo } from '../types/todo'; | ||
|
||
export const getNextTodoId = (todos: Todo[]) => { | ||
return Math.max(...todos.map(todo => todo.id)) + 1; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type Todo = { | ||
id: number, | ||
title: string, | ||
completed: boolean, | ||
userId: number, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type User = { | ||
id: number, | ||
name: string, | ||
username: string, | ||
email: string, | ||
}; |
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.
I think enough just: