From 0a8bc53228a4998f3f6979fd03f8210f689f309b Mon Sep 17 00:00:00 2001 From: Victor Komara Date: Sat, 16 Nov 2024 23:22:31 +0200 Subject: [PATCH 1/2] add task solution --- src/App.tsx | 165 ++++++++++++++++++++++------- src/api/comments.ts | 19 ++++ src/api/posts.ts | 6 ++ src/api/users.ts | 8 ++ src/components/NewCommentForm.tsx | 159 ++++++++++++++++++++++------ src/components/PostDetails.tsx | 166 ++++++++++++++++-------------- src/components/PostsList.tsx | 120 ++++++++++----------- src/components/UserSelector.tsx | 76 ++++++++++---- 8 files changed, 491 insertions(+), 228 deletions(-) create mode 100644 src/api/comments.ts create mode 100644 src/api/posts.ts create mode 100644 src/api/users.ts diff --git a/src/App.tsx b/src/App.tsx index 017957182..701f6684b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,53 +8,144 @@ import { PostsList } from './components/PostsList'; import { PostDetails } from './components/PostDetails'; import { UserSelector } from './components/UserSelector'; import { Loader } from './components/Loader'; +import { useEffect, useState } from 'react'; +import { User } from './types/User'; +import { getUsers } from './api/users'; +import { getPosts } from './api/posts'; +import { Post } from './types/Post'; +import { getComments } from './api/comments'; +import { Comment } from './types/Comment'; -export const App = () => ( -
-
-
-
-
-
- -
+export const App = () => { + const [users, setUsers] = useState([]); + const [userSelected, setUserSelected] = useState(null); + const [posts, setPosts] = useState([]); + const [post, setPost] = useState(null); + const [loadingPosts, setLoadingPosts] = useState(false); + const [errorLoadPosts, setErrorLoadPosts] = useState(false); + const [openPostDetails, setOpenPostDetails] = useState(0); + const [comments, setComments] = useState([]); + const [loadingComments, setLoadingComments] = useState(false); + const [errorLoadComments, setErrorLoadComments] = useState(false); + const [writeCommen, setWriteCommen] = useState(false); -
-

No user selected

+ useEffect(() => { + getUsers().then(setUsers); + }, []); - + function getPostsUser(user: User) { + setLoadingPosts(true); + getPosts(user.id) + .then(setPosts) + .catch(() => { + setErrorLoadPosts(true); + setInterval(() => { + setErrorLoadPosts(false); + }, 3000); + }) + .finally(() => setLoadingPosts(false)); + } -
- Something went wrong! -
+ function getCommentsPost(gettingPost: Post) { + setLoadingComments(true); + getComments(gettingPost.id) + .then(setComments) + .catch(() => { + setErrorLoadComments(true); + setInterval(() => { + setErrorLoadComments(false); + }, 3000); + }) + .finally(() => setLoadingComments(false)); + } + + const showPostsList: boolean | null = + !!posts.length && !errorLoadPosts && userSelected && !loadingPosts; -
- No posts yet + const showNoPostsYet: boolean | null = + posts.length === 0 && !errorLoadPosts && userSelected && !loadingPosts; + + return ( +
+
+
+
+
+
+
- +
+ {users.length > 0 && !userSelected && ( +

No user selected

+ )} + + {loadingPosts && } + + {errorLoadPosts && ( +
+ Something went wrong! +
+ )} + + {showNoPostsYet && ( +
+ No posts yet +
+ )} + + {showPostsList && ( + + )} +
-
-
-
- +
+
+ {post && ( + + )} +
-
-
-); +
+ ); +}; diff --git a/src/api/comments.ts b/src/api/comments.ts new file mode 100644 index 000000000..4299bda67 --- /dev/null +++ b/src/api/comments.ts @@ -0,0 +1,19 @@ +import { Comment } from '../types/Comment'; +import { client } from '../utils/fetchClient'; + +export const getComments = (postId: number) => { + return client.get(`/comments?postId=${postId}`); +}; + +export const createComment = ({ + postId, + name, + email, + body, +}: Omit) => { + return client.post(`/comments`, { postId, name, email, body }); +}; + +export const deleteComment = (commentId: number) => { + return client.delete(`/comments/${commentId}`); +}; diff --git a/src/api/posts.ts b/src/api/posts.ts new file mode 100644 index 000000000..a6a1739ec --- /dev/null +++ b/src/api/posts.ts @@ -0,0 +1,6 @@ +import { Post } from '../types/Post'; +import { client } from '../utils/fetchClient'; + +export const getPosts = (userId: number) => { + return client.get(`/posts?userId=${userId}`); +}; diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 000000000..0cce1db3a --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,8 @@ +import { User } from '../types/User'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 1844; + +export const getUsers = () => { + return client.get(`/users`); +}; diff --git a/src/components/NewCommentForm.tsx b/src/components/NewCommentForm.tsx index 73a8a0b45..f9108d815 100644 --- a/src/components/NewCommentForm.tsx +++ b/src/components/NewCommentForm.tsx @@ -1,8 +1,73 @@ -import React from 'react'; +import classNames from 'classnames'; +import React, { useState } from 'react'; +import { createComment } from '../api/comments'; +import { Comment } from '../types/Comment'; + +type Props = { + postId: number | undefined; + comments: Comment[]; + setComments: (comments: Comment[]) => void; +}; + +export const NewCommentForm: React.FC = ({ + postId, + comments, + setComments, +}) => { + const [authorName, setAuthorName] = useState(''); + const [authorEmail, setAuthorEmail] = useState(''); + const [commentBody, setCommentBody] = useState(''); + + const [errorAuthorName, setErrorAuthorName] = useState(false); + const [errorAuthorEmail, setErrorAuthorEmail] = useState(false); + const [errorCommentBody, setErrorCommentBody] = useState(false); + + const [loadingNewComment, setLoadingNewComment] = useState(false); + + function resetForm() { + setAuthorName(''); + setAuthorEmail(''); + setCommentBody(''); + setErrorAuthorName(false); + setErrorAuthorEmail(false); + setErrorCommentBody(false); + } + + function addComment() { + setErrorAuthorName(!authorName); + setErrorAuthorEmail(!authorEmail); + setErrorCommentBody(!commentBody); + + if (!authorName || !authorEmail || !commentBody) { + return; + } + + setLoadingNewComment(true); + if (postId) { + createComment({ + postId, + name: authorName, + email: authorEmail, + body: commentBody, + }) + .then(res => { + setComments([...comments, res]); + }) + .finally(() => { + setLoadingNewComment(false); + setCommentBody(''); + }); + } + } -export const NewCommentForm: React.FC = () => { return ( -
+ { + event.preventDefault(); + addComment(); + }} + >
-

- Name is required -

+ {errorAuthorName && ( +

+ Name is required +

+ )}
@@ -45,24 +119,33 @@ export const NewCommentForm: React.FC = () => { name="email" id="comment-author-email" placeholder="email@test.com" - className="input is-danger" + className={classNames('input', { 'is-danger': errorAuthorEmail })} + value={authorEmail} + onChange={event => { + setAuthorEmail(event.target.value); + setErrorAuthorEmail(false); + }} /> - - - + {errorAuthorEmail && ( + + + + )}
-

- Email is required -

+ {errorAuthorEmail && ( +

+ Email is required +

+ )}
@@ -75,25 +158,43 @@ export const NewCommentForm: React.FC = () => { id="comment-body" name="body" placeholder="Type comment here" - className="textarea is-danger" + className={classNames('textarea', { + 'is-danger': errorCommentBody, + })} + value={commentBody} + onChange={event => { + setCommentBody(event.target.value); + setErrorCommentBody(false); + }} />
-

- Enter some text -

+ {errorCommentBody && ( +

+ Enter some text +

+ )}
-
{/* eslint-disable-next-line react/button-has-type */} -
diff --git a/src/components/PostDetails.tsx b/src/components/PostDetails.tsx index 2f82db916..8efe0d8d2 100644 --- a/src/components/PostDetails.tsx +++ b/src/components/PostDetails.tsx @@ -1,106 +1,114 @@ import React from 'react'; import { Loader } from './Loader'; import { NewCommentForm } from './NewCommentForm'; +import { Comment } from '../types/Comment'; +import { Post } from '../types/Post'; +import { deleteComment } from '../api/comments'; + +type Props = { + post: Post | null; + comments: Comment[]; + setComments: (comments: Comment[]) => void; + loadingComments: boolean; + errorLoadComments: boolean; + writeCommen: boolean; + setWriteCommen: (writeCommen: boolean) => void; +}; + +export const PostDetails: React.FC = ({ + post, + comments, + setComments, + loadingComments, + errorLoadComments, + writeCommen, + setWriteCommen, +}) => { + function removeDelete(comment: Comment) { + setComments([...comments.filter(commentary => commentary !== comment)]); + deleteComment(comment.id).catch(() => setComments([...comments])); + } -export const PostDetails: React.FC = () => { return (

- #18: voluptate et itaque vero tempora molestiae + #{post?.id}: {post?.title}

-

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

+

{post?.body}

- - -
- Something went wrong -
- -

- No comments yet -

- -

Comments:

+ {loadingComments && } -
-
- - Misha Hrynko - - + {errorLoadComments && !loadingComments && ( +
+ Something went wrong
+ )} -
- Some comment -
-
- -
-
- - Misha Hrynko - + {!comments.length && !errorLoadComments && !loadingComments && ( +

+ No comments yet +

+ )} - -
-
- One more comment -
-
+ {!loadingComments && !!comments.length && ( +

Comments:

+ )} -
-
- - Misha Hrynko - + {!loadingComments && + !!comments.length && + comments.map(comment => { + const { id, email, name, body } = comment; - -
+ return ( +
+
+ + {name} + +
-
- {'Multi\nline\ncomment'} -
-
+
+ {body} +
+
+ ); + })} - + {!writeCommen && !errorLoadComments && !loadingComments && ( + + )}
- + {writeCommen && ( + + )}
); diff --git a/src/components/PostsList.tsx b/src/components/PostsList.tsx index cf90f04b0..122ae4f41 100644 --- a/src/components/PostsList.tsx +++ b/src/components/PostsList.tsx @@ -1,6 +1,22 @@ -import React from 'react'; +import { Post } from '../types/Post'; -export const PostsList: React.FC = () => ( +type Props = { + posts: Post[]; + setPost: (post: Post) => void; + openPostDetails: number; + setOpenPostDetails: (openPostDetails: number) => void; + getCommentsPost: (post: Post) => void; + setWriteCommen: (writeCommen: boolean) => void; +}; + +export const PostsList: React.FC = ({ + posts, + setPost, + openPostDetails, + setOpenPostDetails, + getCommentsPost, + setWriteCommen, +}) => (

Posts:

@@ -15,71 +31,47 @@ export const PostsList: React.FC = () => ( - - 17 - - - fugit voluptas sed molestias voluptatem provident - - - - - - - - - 18 + {posts.map(post => { + const { id, title } = post; - - voluptate et itaque vero tempora molestiae - + return ( + + {id} - - - - + {title} - - 19 - adipisci placeat illum aut reiciendis qui - - - - - - - - 20 - doloribus ad provident suscipit at - - - - - + + {openPostDetails === id ? ( + + ) : ( + + )} + + + ); + })}
diff --git a/src/components/UserSelector.tsx b/src/components/UserSelector.tsx index c89442841..f918017b2 100644 --- a/src/components/UserSelector.tsx +++ b/src/components/UserSelector.tsx @@ -1,16 +1,49 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { User } from '../types/User'; +import classNames from 'classnames'; + +type Props = { + users: User[]; + userSelected: User | null; + setUserSelected: (userSelected: User) => void; + getPostUser: (user: User) => void; + setOpenPostDetails: (openPostDetails: number) => void; + setWriteCommen: (writeCommen: boolean) => void; + setPosts: ([]) => void; + setPost: (post: null) => void; +}; + +export const UserSelector: React.FC = ({ + users, + userSelected, + setUserSelected, + getPostUser, + setOpenPostDetails, + setWriteCommen, + setPosts, + setPost, +}) => { + const [clickedDropdown, setClickedDropdown] = useState(false); -export const UserSelector: React.FC = () => { return ( -
+