diff --git a/README.md b/README.md index c33761fd7..c36849bfe 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ form to add new comments. Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save. > Here is [the working version](https://mate-academy.github.io/react_dynamic-list-of-posts/) +Demo: (https://moskkat40/react_dynamic-list-of-posts/) 1. Learn the `utils/fetchClient.ts` and use it to interact with the API (tests expect that you each API request is sent after 300 ms delay); 1. Initially the `App` shows the `UserSelector` and a paragraph `No user selected` in the main content block. diff --git a/src/App.tsx b/src/App.tsx index 017957182..d03477acd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,5 @@ +/* eslint-disable prettier/prettier */ +/* eslint-disable @typescript-eslint/indent */ import classNames from 'classnames'; import 'bulma/css/bulma.css'; @@ -8,53 +10,174 @@ import { PostsList } from './components/PostsList'; import { PostDetails } from './components/PostDetails'; import { UserSelector } from './components/UserSelector'; import { Loader } from './components/Loader'; +import { useEffect, useMemo, useState } from 'react'; +import * as servicesUsers from './api/users'; +import * as servicesPosts from './api/posts'; +import * as servicesComments from './api/comments'; +import { Post } from './types/Post'; +import { Comment } from './types/Comment'; -export const App = () => ( -
-
-
-
-
-
- -
+export const App = () => { + const [users, setUsers] = useState([]); + const [isUser, setIsUser] = useState(false); + const [userId, setUserId] = useState(null); + const [posts, setPosts] = useState([]); + const [comments, setComments] = useState([]); + const [currentPostId, setCurrentPostId] = useState(0); + const [openPostId, setOpenPostId] = useState(null); + const [isWritePostButton, setIsWritePostButton] = useState(false); + const [loading, setLoading] = useState(''); + const [isError, setIsError] = useState(false); -
-

No user selected

+ useEffect(() => { + setLoading('users'); + servicesUsers + .getUsers() + .then(res => { + setUsers(res); + setIsError(false); + }) + .catch(() => setIsError(true)) + .finally(() => setLoading('')); + }, []); - + useEffect(() => { + if (userId) { + setLoading('posts'); + servicesPosts.getPosts(userId) + .then(res => { + setPosts(res); + setIsError(false); + }) + .catch(() => setIsError(true)) + .finally(() => setLoading('')); + } +}, [userId]); -
- Something went wrong! -
+ const getCommentsByPostId = (postId: number) => { + setLoading('comments'); + setCurrentPostId(postId); + + return servicesComments + .getComments(postId) + .then(res => { + setComments(res); + setIsError(false); + }) + .catch(() => setIsError(true)) + .finally(() => setLoading('')); + }; + + const addComment = ({ name, email, body, postId }: Comment) => { + setLoading('addComment'); -
- No posts yet + return servicesComments + .createComment({ name, email, body, postId }) + .then(newComment => { + setComments(currentComments => [...currentComments, newComment]); + setIsError(false); + }) + .catch(() => setIsError(true)) + .finally(() => setLoading('')); + }; + + const deleteComment = (commentId: number) => { + servicesComments + .deleteComment(commentId) + .then(() => + setComments(currentComments => + currentComments.filter(comment => comment.id !== commentId), + ), + ); + }; + + const currentPost = useMemo(() => { + return posts.find((post: Post) => post.id === currentPostId); + }, [currentPostId, posts]); + + return ( +
+
+
+
+
+
+
+ {loading === 'users' && } +
+

+ {!isUser && loading !== 'users' && 'No user selected'} +

- -
-
-
+ {loading === 'posts' && } + {isError && ( +
+ Something went wrong! +
+ )} -
-
- + {posts.length === 0 && + isUser && + loading !== 'posts' && + !isError && ( +
+ No posts yet +
+ )} + + {isUser && + posts.length > 0 && + loading !== 'posts' && + !isError && ( + + )} +
+
+
0 && openPostId} + )} + > +
+ +
+
-
-
-); + + ); +}; diff --git a/src/api/comments.ts b/src/api/comments.ts new file mode 100644 index 000000000..084b04a39 --- /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 = ({ + name, + email, + body, + postId, +}: Omit) => { + return client.post('/comments', { name, email, body, postId }); +}; + +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..c0733b040 --- /dev/null +++ b/src/api/posts.ts @@ -0,0 +1,5 @@ +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..70a263bb9 --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,5 @@ +import { client } from '../utils/fetchClient'; + +export const getUsers = () => { + return client.get('/users'); +}; diff --git a/src/components/NewCommentForm.tsx b/src/components/NewCommentForm.tsx index 73a8a0b45..5b10d982a 100644 --- a/src/components/NewCommentForm.tsx +++ b/src/components/NewCommentForm.tsx @@ -1,6 +1,87 @@ -import React from 'react'; +import classNames from 'classnames'; +import React, { useState } from 'react'; +import { Comment } from '../types/Comment'; + +type Props = { + addComment: (a: Comment) => Promise; + postId: number; + loadingAddNewComment: boolean; +}; + +export const NewCommentForm: React.FC = ({ + addComment, + postId, + loadingAddNewComment, +}) => { + const [name, setName] = useState(''); + const [titleError, setTitleError] = useState(false); + + const [email, setEmail] = useState(''); + const [emailError, setEmailError] = useState(false); + + const [body, setBody] = useState(''); + const [bodyError, setBodyError] = useState(false); + + const [isReset, setIsReset] = useState(false); + + function reset() { + setBody(''); + } + + function resetAllFields() { + setIsReset(true); + setName(''); + setEmail(''); + setBody(''); + } + + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + setTitleError(false); + }; + + const handleEmailChange = (event: React.ChangeEvent) => { + setEmail(event.target.value); + setEmailError(false); + }; + + const handleBodyChange = (event: React.ChangeEvent) => { + setBody(event.target.value); + setBodyError(false); + }; + + const handleSubmit = (event: React.MouseEvent) => { + event.preventDefault(); + + const trimmedName = name.trim(); + const trimmedEmail = email.trim(); + const trimmedBody = body.trim(); + + if (name.length === 0 || !trimmedName) { + setTitleError(true); + } + + if (email.length === 0 || !trimmedEmail) { + setEmailError(true); + } + + if (body.length === 0 || !trimmedBody) { + setBodyError(true); + } + + if (name.length > 0 && email.length > 0 && body.length > 0) { + addComment({ + name, + email, + body, + postId, + id: 0, + }).then(() => { + reset(); + }); + } + }; -export const NewCommentForm: React.FC = () => { return (
@@ -12,26 +93,34 @@ export const NewCommentForm: React.FC = () => { - - - + {titleError && !isReset && ( + + + + )}
-

- Name is required -

+ {titleError && !isReset && ( +

+ Name is required +

+ )}
@@ -41,28 +130,36 @@ export const NewCommentForm: React.FC = () => {
- - - + {emailError && !isReset && ( + + + + )}
-

- Email is required -

+ {emailError && !isReset && ( +

+ Email is required +

+ )}
@@ -74,26 +171,42 @@ export const NewCommentForm: React.FC = () => {