-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
add solution #1203
base: master
Are you sure you want to change the base?
add solution #1203
Changes from 1 commit
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 |
---|---|---|
|
@@ -8,53 +8,120 @@ import { PostsList } from './components/PostsList'; | |
import { PostDetails } from './components/PostDetails'; | ||
import { UserSelector } from './components/UserSelector'; | ||
import { Loader } from './components/Loader'; | ||
import { User } from './types/User'; | ||
import { useEffect, useState } from 'react'; | ||
import { Post } from './types/Post'; | ||
import { getPosts } from './api/api'; | ||
|
||
export const App = () => ( | ||
<main className="section"> | ||
<div className="container"> | ||
<div className="tile is-ancestor"> | ||
<div className="tile is-parent"> | ||
<div className="tile is-child box is-success"> | ||
<div className="block"> | ||
<UserSelector /> | ||
</div> | ||
export const App = () => { | ||
const [selectedUser, setSelectedUser] = useState<User | null>(null); | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [isError, setIsError] = useState(false); | ||
const [posts, setPosts] = useState<Post[]>([]); | ||
const [openedPost, setOpenedPost] = useState<Post | null>(null); | ||
|
||
<div className="block" data-cy="MainContent"> | ||
<p data-cy="NoSelectedUser">No user selected</p> | ||
const selectUser = (user: User) => { | ||
setOpenedPost(null); | ||
setPosts([]); | ||
setIsLoading(true); | ||
setSelectedUser(user); | ||
}; | ||
|
||
<Loader /> | ||
useEffect(() => { | ||
if (selectedUser) { | ||
getPosts(selectedUser.id) | ||
.then(result => { | ||
const results = result.map(post => ({ | ||
id: post.id, | ||
userId: post.userId, | ||
title: post.title, | ||
body: post.body, | ||
})); | ||
|
||
<div | ||
className="notification is-danger" | ||
data-cy="PostsLoadingError" | ||
> | ||
Something went wrong! | ||
</div> | ||
setPosts(results); | ||
}) | ||
.catch(() => { | ||
setIsError(true); | ||
// setTimeout(() => setIsError(false), 3000) | ||
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. This line is commented out. If the intention is to reset the error state after a delay, consider uncommenting it or implementing a similar mechanism to ensure the error message does not persist indefinitely. |
||
}) | ||
.finally(() => { | ||
setIsLoading(false); | ||
}); | ||
} | ||
}, [selectedUser]); | ||
|
||
const chosePost = (post: Post | null) => { | ||
setOpenedPost(post); | ||
}; | ||
|
||
<div className="notification is-warning" data-cy="NoPostsYet"> | ||
No posts yet | ||
return ( | ||
<main className="section"> | ||
<div className="container"> | ||
<div className="tile is-ancestor"> | ||
<div className="tile is-parent"> | ||
<div className="tile is-child box is-success"> | ||
<div className="block"> | ||
<UserSelector onSelectUser={selectUser} /> | ||
</div> | ||
|
||
<PostsList /> | ||
<div className="block" data-cy="MainContent"> | ||
{!selectedUser && ( | ||
<p data-cy="NoSelectedUser">No user selected</p> | ||
)} | ||
|
||
{isLoading ? ( | ||
<Loader /> | ||
) : ( | ||
<> | ||
{isError && ( | ||
<div | ||
className="notification is-danger" | ||
data-cy="PostsLoadingError" | ||
> | ||
Something went wrong! | ||
</div> | ||
)} | ||
|
||
{posts.length === 0 && selectedUser && !isError && ( | ||
<div | ||
className="notification is-warning" | ||
data-cy="NoPostsYet" | ||
> | ||
No posts yet | ||
</div> | ||
)} | ||
|
||
{posts.length > 0 && ( | ||
<PostsList | ||
posts={posts} | ||
onOpenSideBar={chosePost} | ||
openedPost={openedPost} | ||
/> | ||
)} | ||
</> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div | ||
data-cy="Sidebar" | ||
className={classNames( | ||
'tile', | ||
'is-parent', | ||
'is-8-desktop', | ||
'Sidebar', | ||
'Sidebar--open', | ||
)} | ||
> | ||
<div className="tile is-child box is-success "> | ||
<PostDetails /> | ||
<div | ||
data-cy="Sidebar" | ||
className={classNames( | ||
'tile', | ||
'is-parent', | ||
'is-8-desktop', | ||
'Sidebar', | ||
{ | ||
'Sidebar--open': openedPost, | ||
}, | ||
)} | ||
> | ||
<div className="tile is-child box is-success "> | ||
{openedPost && <PostDetails post={openedPost} />} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</main> | ||
); | ||
</main> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Comment, CommentData } from '../types/Comment'; | ||
import { Post } from '../types/Post'; | ||
import { User } from '../types/User'; | ||
import { client } from '../utils/fetchClient'; | ||
|
||
export const getUsers = () => { | ||
return client.get<User[]>('/users'); | ||
}; | ||
|
||
export const getPosts = (userId: number) => { | ||
return client.get<Post[]>(`/posts?userId=${userId}`); | ||
}; | ||
|
||
export const getComments = (postId: number) => { | ||
return client.get<Comment[]>(`/comments?postId=${postId}`); | ||
}; | ||
|
||
export const deleteComment = (commentId: number) => { | ||
return client.delete(`/comments/${commentId}`); | ||
}; | ||
Comment on lines
+18
to
+20
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. The |
||
|
||
export const createComment = (data: CommentData) => { | ||
return client.post<Comment>('/comments', data); | ||
}; | ||
Comment on lines
+22
to
+24
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. The |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,76 @@ | ||
import React from 'react'; | ||
import React, { FormEvent, useState } from 'react'; | ||
import { Comment, CommentData } from '../types/Comment'; | ||
import classNames from 'classnames'; | ||
import { createComment } from '../api/api'; | ||
|
||
type Props = { | ||
postId: number; | ||
addComment: (comment: Comment) => void; | ||
}; | ||
|
||
export const NewCommentForm: React.FC<Props> = ({ postId, addComment }) => { | ||
const [comment, setComment] = useState<CommentData>({ | ||
email: '', | ||
body: '', | ||
name: '', | ||
postId, | ||
}); | ||
const [isNameError, setIsNameError] = useState(false); | ||
const [isEmailError, setIsEmailError] = useState(false); | ||
const [isBodyError, setIsBodyError] = useState(false); | ||
const [isLoading, setIsLoading] = useState(false); | ||
|
||
const clearAllError = () => { | ||
setIsLoading(false); | ||
setIsBodyError(false); | ||
setIsNameError(false); | ||
setIsEmailError(false); | ||
}; | ||
|
||
const submitComment = (e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
|
||
if (!comment.name) { | ||
setIsNameError(true); | ||
} | ||
|
||
if (!comment.email) { | ||
setIsEmailError(true); | ||
} | ||
|
||
if (!comment.body) { | ||
setIsBodyError(true); | ||
} | ||
|
||
if (!comment.name || !comment.email || !comment.body) { | ||
return; | ||
} | ||
|
||
setIsLoading(true); | ||
|
||
createComment(comment) | ||
.then(result => { | ||
addComment({ ...comment, id: result.id }); | ||
}) | ||
.catch() | ||
.finally(() => { | ||
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. The |
||
clearAllError(); | ||
setComment(prev => ({ ...prev, body: '' })); | ||
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. The |
||
}); | ||
}; | ||
|
||
const reset = () => { | ||
clearAllError(); | ||
setComment({ | ||
email: '', | ||
body: '', | ||
name: '', | ||
postId, | ||
}); | ||
}; | ||
|
||
export const NewCommentForm: React.FC = () => { | ||
return ( | ||
<form data-cy="NewCommentForm"> | ||
<form data-cy="NewCommentForm" onSubmit={submitComment} onReset={reset}> | ||
<div className="field" data-cy="NameField"> | ||
<label className="label" htmlFor="comment-author-name"> | ||
Author Name | ||
|
@@ -14,24 +82,34 @@ export const NewCommentForm: React.FC = () => { | |
name="name" | ||
id="comment-author-name" | ||
placeholder="Name Surname" | ||
className="input is-danger" | ||
className={classNames('input', { | ||
'is-danger': isNameError && !comment.name, | ||
})} | ||
value={comment.name} | ||
onChange={e => | ||
setComment(prev => ({ ...prev, name: e.target.value })) | ||
} | ||
/> | ||
|
||
<span className="icon is-small is-left"> | ||
<i className="fas fa-user" /> | ||
</span> | ||
|
||
<span | ||
className="icon is-small is-right has-text-danger" | ||
data-cy="ErrorIcon" | ||
> | ||
<i className="fas fa-exclamation-triangle" /> | ||
</span> | ||
{isNameError && !comment.name && ( | ||
<span | ||
className="icon is-small is-right has-text-danger" | ||
data-cy="ErrorIcon" | ||
> | ||
<i className="fas fa-exclamation-triangle" /> | ||
</span> | ||
)} | ||
</div> | ||
|
||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Name is required | ||
</p> | ||
{isNameError && !comment.name && ( | ||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Name is required | ||
</p> | ||
)} | ||
</div> | ||
|
||
<div className="field" data-cy="EmailField"> | ||
|
@@ -45,24 +123,34 @@ export const NewCommentForm: React.FC = () => { | |
name="email" | ||
id="comment-author-email" | ||
placeholder="[email protected]" | ||
className="input is-danger" | ||
className={classNames('input', { | ||
'is-danger': isEmailError && !comment.email, | ||
})} | ||
value={comment.email} | ||
onChange={e => | ||
setComment(prev => ({ ...prev, email: e.target.value })) | ||
} | ||
/> | ||
|
||
<span className="icon is-small is-left"> | ||
<i className="fas fa-envelope" /> | ||
</span> | ||
|
||
<span | ||
className="icon is-small is-right has-text-danger" | ||
data-cy="ErrorIcon" | ||
> | ||
<i className="fas fa-exclamation-triangle" /> | ||
</span> | ||
{isEmailError && !comment.email && ( | ||
<span | ||
className="icon is-small is-right has-text-danger" | ||
data-cy="ErrorIcon" | ||
> | ||
<i className="fas fa-exclamation-triangle" /> | ||
</span> | ||
)} | ||
</div> | ||
|
||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Email is required | ||
</p> | ||
{isEmailError && !comment.email && ( | ||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Email is required | ||
</p> | ||
)} | ||
</div> | ||
|
||
<div className="field" data-cy="BodyField"> | ||
|
@@ -75,18 +163,31 @@ export const NewCommentForm: React.FC = () => { | |
id="comment-body" | ||
name="body" | ||
placeholder="Type comment here" | ||
className="textarea is-danger" | ||
className={classNames('textarea', { | ||
'is-danger': isBodyError && !comment.body, | ||
})} | ||
value={comment.body} | ||
onChange={e => | ||
setComment(prev => ({ ...prev, body: e.target.value })) | ||
} | ||
/> | ||
</div> | ||
|
||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Enter some text | ||
</p> | ||
{isBodyError && !comment.body && ( | ||
<p className="help is-danger" data-cy="ErrorMessage"> | ||
Enter some text | ||
</p> | ||
)} | ||
</div> | ||
|
||
<div className="field is-grouped"> | ||
<div className="control"> | ||
<button type="submit" className="button is-link is-loading"> | ||
<button | ||
type="submit" | ||
className={classNames('button is-link', { | ||
'is-loading': isLoading, | ||
})} | ||
> | ||
Add | ||
</button> | ||
</div> | ||
|
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.
Consider resetting the
isError
state after a certain period or when a new user is selected. This will improve user experience by clearing the error message once the issue is resolved or a new action is taken.