From 9850a3fca0ea00e692f007575cbeec6fd95a251e Mon Sep 17 00:00:00 2001 From: Pavlo Petrashevskyi Date: Fri, 1 Sep 2023 11:09:35 +0300 Subject: [PATCH 1/5] app was created --- src/App.tsx | 98 ++++++++++++------ src/components/Comment.tsx | 51 ++++++++++ src/components/NewCommentForm.tsx | 161 ++++++++++++++++++++++++------ src/components/Post.tsx | 47 +++++++++ src/components/PostDetails.tsx | 161 +++++++++++++----------------- src/components/PostsList.tsx | 89 ++++------------- src/components/User.tsx | 27 +++++ src/components/UserSelector.tsx | 58 +++++++++-- src/types/Comment.ts | 9 -- src/types/CommentInt.ts | 11 ++ src/types/{Post.ts => PostInt.ts} | 2 +- src/types/{User.ts => UserInt.ts} | 2 +- src/utils/RESTmethods.ts | 22 ++++ src/utils/fetchClient.ts | 14 +-- 14 files changed, 508 insertions(+), 244 deletions(-) create mode 100644 src/components/Comment.tsx create mode 100644 src/components/Post.tsx create mode 100644 src/components/User.tsx delete mode 100644 src/types/Comment.ts create mode 100644 src/types/CommentInt.ts rename src/types/{Post.ts => PostInt.ts} (71%) rename src/types/{User.ts => UserInt.ts} (70%) create mode 100644 src/utils/RESTmethods.ts diff --git a/src/App.tsx b/src/App.tsx index db56b44b0..705a6dab6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import 'bulma/bulma.sass'; import '@fortawesome/fontawesome-free/css/all.css'; import './App.scss'; @@ -7,9 +7,28 @@ import classNames from 'classnames'; import { PostsList } from './components/PostsList'; import { PostDetails } from './components/PostDetails'; import { UserSelector } from './components/UserSelector'; +import { UserInt } from './types/UserInt'; +import { PostInt } from './types/PostInt'; +import { getUserPost } from './utils/RESTmethods'; import { Loader } from './components/Loader'; export const App: React.FC = () => { + const [selectedUser, setSelectedUser] = useState(); + const [selectedPost, setSelectedPost] = useState(); + const [posts, setPosts] = useState(); + const [isError, setIsError] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (selectedUser) { + setIsLoading(true); + getUserPost(selectedUser.id) + .then(setPosts) + .catch(() => setIsError(true)) + .finally(() => setIsLoading(false)); + } + }, [selectedUser]); + return (
@@ -17,46 +36,67 @@ export const App: React.FC = () => {
- +
-

- No user selected -

+ {!selectedUser && !isError && ( +

+ No user selected +

+ )} + + {isLoading && } - + {!isLoading && isError && ( +
+ Something went wrong! +
+ )} -
- Something went wrong! -
+ {!isLoading && posts && posts.length === 0 && ( +
+ No posts yet +
+ )} -
- No posts yet -
+ {!isLoading && posts && posts.length > 0 && ( + + )} -
-
-
- + {selectedPost && ( +
+
+ +
-
+ )}
diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx new file mode 100644 index 000000000..94c2d65ef --- /dev/null +++ b/src/components/Comment.tsx @@ -0,0 +1,51 @@ +import React, { Dispatch, SetStateAction } from 'react'; +import { CommentInt } from '../types/CommentInt'; +import { deleteComment } from '../utils/RESTmethods'; + +type CommentProps = { + comment: CommentInt, + setComments: Dispatch>, +}; + +export const Comment: React.FC = ({ + comment, + setComments, +}) => { + const { + email, + name, + body, + id, + } = comment; + + const handleDeleteComment = (commentId: number) => { + setComments(prevCom => [...prevCom].filter(com => com.id !== commentId)); + deleteComment(commentId); + }; + + return ( +
+
+ + {name} + + +
+ +
+ {body} +
+
+ ); +}; diff --git a/src/components/NewCommentForm.tsx b/src/components/NewCommentForm.tsx index 73a8a0b45..44cad6a41 100644 --- a/src/components/NewCommentForm.tsx +++ b/src/components/NewCommentForm.tsx @@ -1,8 +1,71 @@ -import React from 'react'; +import React, { Dispatch, SetStateAction, useState } from 'react'; +import classNames from 'classnames'; +import { addComment } from '../utils/RESTmethods'; +import { CommentInt } from '../types/CommentInt'; + +type NewCommentFormProps = { + postId: number, + setComments: Dispatch>, +}; + +export const NewCommentForm: React.FC = ({ + postId, + setComments, +}) => { + const [name, setName] = useState(''); + const [body, setBody] = useState(''); + const [email, setEmail] = useState(''); + const [isNameAllright, setIsNameAllright] = useState(true); + const [isEmailAllright, setIsEmailAllright] = useState(true); + const [isBodyAllright, setIsBodyAllright] = useState(true); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (name.trim() && body.trim() && email.trim()) { + setIsLoading(true); + + addComment({ + postId, + name, + email, + body, + }) + .then(res => { + setComments(prevCom => [...prevCom, res]); + setBody(''); + }) + .finally(() => setIsLoading(false)); + } + + if (!name.trim()) { + setIsNameAllright(false); + } + + if (!body.trim()) { + setIsBodyAllright(false); + } + + if (!email.trim()) { + setIsEmailAllright(false); + } + }; + + const handleClear = () => { + setBody(''); + setName(''); + setEmail(''); + setIsNameAllright(true); + setIsEmailAllright(true); + setIsBodyAllright(true); + }; -export const NewCommentForm: React.FC = () => { return ( -
+
-

- Name is required -

+ {!isNameAllright && ( +

+ Name is required +

+ )}
@@ -44,25 +118,36 @@ export const NewCommentForm: React.FC = () => { type="text" name="email" id="comment-author-email" + value={email} placeholder="email@test.com" - className="input is-danger" + className={classNames('input', { + 'is-danger': !isEmailAllright, + })} + onChange={(e) => { + setEmail(e.target.value); + setIsEmailAllright(true); + }} /> - - - + {!isEmailAllright && ( + + + + )}
-

- Email is required -

+ {!isEmailAllright && ( +

+ Email is required +

+ )}
@@ -75,25 +160,43 @@ export const NewCommentForm: React.FC = () => { id="comment-body" name="body" placeholder="Type comment here" - className="textarea is-danger" + value={body} + className={classNames('textarea', { + 'is-danger': !isBodyAllright, + })} + onChange={(e) => { + setBody(e.target.value); + setIsBodyAllright(true); + }} />
-

- Enter some text -

+ {!isBodyAllright && ( +

+ Enter some text +

+ )}
-
{/* eslint-disable-next-line react/button-has-type */} -
diff --git a/src/components/Post.tsx b/src/components/Post.tsx new file mode 100644 index 000000000..36b40e78a --- /dev/null +++ b/src/components/Post.tsx @@ -0,0 +1,47 @@ +import classNames from 'classnames'; +import { PostInt } from '../types/PostInt'; + +type PostProps = { + post: PostInt, + selectedPost: PostInt | undefined, + setSelectedPost: (newPost: PostInt | undefined) => void, +}; + +export const Post: React.FC = ({ + post, + selectedPost, + setSelectedPost, +}) => { + const handleSelectPost = (newPost: PostInt | undefined) => { + setSelectedPost(newPost); + }; + + const isPostSelected = post.id === selectedPost?.id; + + return ( + + {post.id} + + + {post.title} + + + + + + + ); +}; diff --git a/src/components/PostDetails.tsx b/src/components/PostDetails.tsx index ace945f0a..b8d6be0ea 100644 --- a/src/components/PostDetails.tsx +++ b/src/components/PostDetails.tsx @@ -1,117 +1,96 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Loader } from './Loader'; import { NewCommentForm } from './NewCommentForm'; +import { PostInt } from '../types/PostInt'; +import { getPostComments } from '../utils/RESTmethods'; +import { CommentInt } from '../types/CommentInt'; +import { Comment } from './Comment'; + +type PostDetailProps = { + selectedPost: PostInt, +}; + +export const PostDetails: React.FC = ({ + selectedPost, +}) => { + const { id, body, title } = selectedPost; + const [comments, setComments] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isWritingComment, setIsWritingComment] = useState(false); + const [isError, setIsError] = useState(false); + + const handleCommentWriting = () => setIsWritingComment(!isWritingComment); + + useEffect(() => { + setIsLoading(true); + setIsWritingComment(false); + + getPostComments(id) + .then(setComments) + .catch(() => setIsError(true)) + .finally(() => setIsLoading(false)); + }, [selectedPost]); -export const PostDetails: React.FC = () => { return (

- #18: voluptate et itaque vero tempora molestiae + {`#${id}: ${title}`}

- eveniet quo quis - laborum totam consequatur non dolor - ut et est repudiandae - est voluptatem vel debitis et magnam + {`${body}`}

- - -
- Something went wrong -
- -

- No comments yet -

- -

Comments:

+ {isLoading && } -
-
- - Misha Hrynko - - + {!isLoading && isError && ( +
+ Something went wrong
+ )} -
- Some comment -
-
- - + {!isLoading && !isError && comments.length === 0 && ( +

+ No comments yet +

+ )} -
-
- - Misha Hrynko - + {!isLoading && !isError && comments.length > 0 && ( + <> +

Comments:

- -
+ {comments.map(comment => ( + + ))} + + )} -
- {'Multi\nline\ncomment'} -
-
- - + {!isLoading && !isError && !isWritingComment && ( + + )}
- + {!isLoading && isWritingComment && ( + + )}
); diff --git a/src/components/PostsList.tsx b/src/components/PostsList.tsx index 6e6efc0f3..c985c045d 100644 --- a/src/components/PostsList.tsx +++ b/src/components/PostsList.tsx @@ -1,6 +1,18 @@ import React from 'react'; - -export const PostsList: React.FC = () => ( +import { PostInt } from '../types/PostInt'; +import { Post } from './Post'; + +type PostsListProps = { + posts: PostInt[], + selectedPost: PostInt | undefined, + setSelectedPost: (newPost: PostInt | undefined) => void, +}; + +export const PostsList: React.FC = ({ + posts, + selectedPost, + setSelectedPost, +}) => (

Posts:

@@ -14,71 +26,14 @@ export const PostsList: React.FC = () => ( - - 17 - - - fugit voluptas sed molestias voluptatem provident - - - - - - - - - 18 - - - voluptate et itaque vero tempora molestiae - - - - - - - - - 19 - adipisci placeat illum aut reiciendis qui - - - - - - - - 20 - doloribus ad provident suscipit at - - - - - + {posts.map(post => ( + + ))}
diff --git a/src/components/User.tsx b/src/components/User.tsx new file mode 100644 index 000000000..0c9df4a8f --- /dev/null +++ b/src/components/User.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import classnames from 'classnames'; +import { UserInt } from '../types/UserInt'; + +type UserProps = { + user: UserInt, + selectedUser: UserInt | undefined, + handleSelectUser: (newUser: UserInt) => void, +}; + +export const User: React.FC = ({ + user, + selectedUser, + handleSelectUser, +}) => { + return ( + handleSelectUser(user)} + > + {user.name} + + ); +}; diff --git a/src/components/UserSelector.tsx b/src/components/UserSelector.tsx index cb83a8f68..0df6ca729 100644 --- a/src/components/UserSelector.tsx +++ b/src/components/UserSelector.tsx @@ -1,6 +1,36 @@ -import React from 'react'; +import { useEffect, useState } from 'react'; +import { UserInt } from '../types/UserInt'; +import { getUser } from '../utils/RESTmethods'; +import { User } from './User'; + +type UserSelectorProps = { + selectedUser: UserInt | undefined, + setSelectedUser: (newUser: UserInt) => void, + setIsError: (isError: boolean) => void, +}; + +export const UserSelector: React.FC = ({ + selectedUser, + setSelectedUser, + setIsError, +}) => { + const [users, setUsers] = useState([]); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); + + useEffect(() => { + getUser() + .then(setUsers) + .catch(() => { + setIsError(true); + }); + }, []); + + const handleDropdownAppear = () => setIsDropdownVisible(!isDropdownVisible); + const handleSelectUser = (newUser: UserInt) => { + setSelectedUser(newUser); + handleDropdownAppear(); + }; -export const UserSelector: React.FC = () => { return (
{ className="button" aria-haspopup="true" aria-controls="dropdown-menu" + onClick={() => handleDropdownAppear()} > - Choose a user + {selectedUser ? selectedUser.name : 'Choose a user'}