diff --git a/src/App.scss b/src/App.scss index 695435da4..a2fd3ddf9 100644 --- a/src/App.scss +++ b/src/App.scss @@ -21,3 +21,7 @@ .message-body { white-space: pre-line; } + +iframe { + display: none; +} diff --git a/src/App.tsx b/src/App.tsx index db56b44b0..c2cfbfe54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,30 @@ -import React from 'react'; -import 'bulma/bulma.sass'; +/* eslint-disable max-len */ import '@fortawesome/fontawesome-free/css/all.css'; +import 'bulma/bulma.sass'; +import React from 'react'; import './App.scss'; import classNames from 'classnames'; -import { PostsList } from './components/PostsList'; +import { Loader } from './components/Loader'; import { PostDetails } from './components/PostDetails'; +import { PostsList } from './components/PostsList'; import { UserSelector } from './components/UserSelector'; -import { Loader } from './components/Loader'; +import { usePostsContext } from './hooks/usePostsContext'; +import { useUserContext } from './hooks/useUserContext'; export const App: React.FC = () => { + const { + posts, + isPostsError, + isPostsPending, + selectedPost, + } = usePostsContext(); + const { selectedUser } = useUserContext(); + + const hasError = !isPostsPending && isPostsError; + const withoutErrors = selectedUser && !isPostsError; + const fetchedWithoutErrors = withoutErrors && !isPostsPending; + return (
@@ -21,24 +36,28 @@ export const App: React.FC = () => {
-

- No user selected -

- - - -
- Something went wrong! -
- -
- No posts yet -
- - + {!selectedUser && ( +

+ No user selected +

+ )} + {withoutErrors && isPostsPending && ()} + {fetchedWithoutErrors && (posts.length > 0 + ? + : ( +
+ No posts yet +
+ ) + )} + {hasError && ( +
+ Something went wrong! +
+ )}
@@ -50,11 +69,13 @@ export const App: React.FC = () => { 'is-parent', 'is-8-desktop', 'Sidebar', - 'Sidebar--open', + { + 'Sidebar--open': selectedPost, + }, )} >
- + {selectedPost && }
diff --git a/src/Root.tsx b/src/Root.tsx new file mode 100644 index 000000000..facd994c3 --- /dev/null +++ b/src/Root.tsx @@ -0,0 +1,16 @@ +import { App } from './App'; +import { CommentsContextProvider } from './context/CommentsContext'; +import { PostsContextProvider } from './context/PostsContext'; +import { UserContextProvider } from './context/UserContext'; + +const Root: React.FC = () => ( + + + + + + + +); + +export default Root; diff --git a/src/components/NewCommentForm.tsx b/src/components/NewCommentForm.tsx index 73a8a0b45..314b50881 100644 --- a/src/components/NewCommentForm.tsx +++ b/src/components/NewCommentForm.tsx @@ -1,8 +1,67 @@ -import React from 'react'; +import classNames from 'classnames'; +import React, { useState } from 'react'; +import { useCommentsContext } from '../hooks/useCommentsContext'; + +const initialDetails = { + name: '', + email: '', + body: '', +}; export const NewCommentForm: React.FC = () => { + const { addingComment, handleAddComment } = useCommentsContext(); + const [commentDetails, setCommentDetails] = useState(initialDetails); + const [nameError, setNameError] = useState(false); + const [emailError, setEmailError] = useState(false); + const [bodyError, setBodyError] = useState(false); + + const handleResetForm = () => { + setNameError(false); + setEmailError(false); + setBodyError(false); + setCommentDetails(prev => ({ + ...prev, + body: '', + })); + }; + + const handleSubmitForm = (event: React.FormEvent) => { + event.preventDefault(); + + if (!commentDetails.name) { + setNameError(true); + } + + if (!commentDetails.email) { + setEmailError(true); + } + + if (!commentDetails.body) { + setBodyError(true); + } + + if (commentDetails.name + && commentDetails.email + && commentDetails.body + ) { + handleAddComment(commentDetails); + handleResetForm(); + } + }; + + const handleDetailsChange = (fieldName: string, value: string) => { + setCommentDetails(prev => ({ + ...prev, + [fieldName]: value, + })); + }; + return ( -
+
-

- Name is required -

+ {nameError && ( +

+ Name is required +

+ )}
@@ -45,24 +112,32 @@ export const NewCommentForm: React.FC = () => { name="email" id="comment-author-email" placeholder="email@test.com" - className="input is-danger" + className={classNames('input', { + 'is-danger': emailError, + })} + value={commentDetails.email} + onChange={e => handleDetailsChange(e.target.name, e.target.value)} /> - - - + {emailError && ( + + + + )}
-

- Email is required -

+ {emailError && ( +

+ Email is required +

+ )}
@@ -75,25 +150,39 @@ export const NewCommentForm: React.FC = () => { id="comment-body" name="body" placeholder="Type comment here" - className="textarea is-danger" + className={classNames('textarea', { + 'is-danger': bodyError, + })} + value={commentDetails.body} + onChange={e => handleDetailsChange(e.target.name, e.target.value)} />
-

- Enter some text -

+ {bodyError && ( +

+ Enter some text +

+ )}
-
{/* eslint-disable-next-line react/button-has-type */} -
diff --git a/src/components/PostDetails.tsx b/src/components/PostDetails.tsx index ace945f0a..7c69548db 100644 --- a/src/components/PostDetails.tsx +++ b/src/components/PostDetails.tsx @@ -1,118 +1,107 @@ -import React from 'react'; +/* eslint-disable react/jsx-one-expression-per-line */ +import React, { useEffect, useState } from 'react'; +import { useCommentsContext } from '../hooks/useCommentsContext'; +import { usePostsContext } from '../hooks/usePostsContext'; import { Loader } from './Loader'; import { NewCommentForm } from './NewCommentForm'; export const PostDetails: React.FC = () => { + const { selectedPost } = usePostsContext(); + const { + comments, + isCommentsError, + isCommentsPending, + handleDeleteComment, + } = useCommentsContext(); + + const [addPost, setAddPost] = useState(false); + + const hasError = isCommentsError && !isCommentsPending; + const withoutError = selectedPost && !isCommentsError; + const pending = withoutError && isCommentsPending; + const fetched = withoutError && !isCommentsPending; + + useEffect(() => { + setAddPost(false); + }, [selectedPost]); + return (
-
-
-

- #18: voluptate et itaque vero tempora molestiae -

+
+

+ #{selectedPost?.id}: {selectedPost?.title} +

-

- eveniet quo quis - laborum totam consequatur non dolor - ut et est repudiandae - est voluptatem vel debitis et magnam -

-
- -
- +

+ {selectedPost?.body} +

+
+
+ {pending && } + {hasError && (
Something went wrong
+ )} + {fetched && ( + <> + {comments.length > 0 + ? ( + <> +

Comments:

+ {comments.map(comment => ( +
+
+ + {comment.name} + -

- No comments yet -

- -

Comments:

- - - -
-
- - Misha Hrynko - + +
+
+ {comment.body} +
+
+ ))} + + ) + : ( +

+ No comments yet +

+ )} + {!addPost && ( -
-
- One more comment -
-
- - - - -
- - + )} + + )}
+ + {addPost && }
); }; diff --git a/src/components/PostsList.tsx b/src/components/PostsList.tsx index 6e6efc0f3..cc9dece6c 100644 --- a/src/components/PostsList.tsx +++ b/src/components/PostsList.tsx @@ -1,85 +1,50 @@ +import classNames from 'classnames'; import React from 'react'; - -export const PostsList: React.FC = () => ( -
-

Posts:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Title
17 - fugit voluptas sed molestias voluptatem provident - - -
18 - voluptate et itaque vero tempora molestiae - - -
19adipisci placeat illum aut reiciendis qui - -
20doloribus ad provident suscipit at - -
-
-); +import { usePostsContext } from '../hooks/usePostsContext'; + +export const PostsList: React.FC = () => { + const { posts, selectedPost, handleSelectPost } = usePostsContext(); + + return ( +
+

Posts:

+ + + + + + + + + + + + {posts.map(post => ( + + + + + + + + ))} + +
#Title
{post.id} + {post.title} + + +
+
+ ); +}; diff --git a/src/components/UserSelector.tsx b/src/components/UserSelector.tsx index cb83a8f68..f5a431cde 100644 --- a/src/components/UserSelector.tsx +++ b/src/components/UserSelector.tsx @@ -1,10 +1,39 @@ -import React from 'react'; +import classNames from 'classnames'; +import React, { useEffect, useRef, useState } from 'react'; +import { useUserContext } from '../hooks/useUserContext'; +import { User } from '../types/User'; export const UserSelector: React.FC = () => { + const { + users, + onSelect, + selectedUser, + } = useUserContext(); + const [showDropdown, setShowDropdown] = useState(false); + const dropdownRef = useRef(null); + + const handleSelectUser = (user: User) => { + setShowDropdown(false); + onSelect(user); + }; + + useEffect(() => { + const closeDropdown = (event: MouseEvent) => { + if (!dropdownRef.current?.contains(event.target as Node)) { + setShowDropdown(false); + } + }; + + window.addEventListener('click', closeDropdown); + }, []); + return (