From 72fc2c11e134354f07b553dedcedf30a9a5f8256 Mon Sep 17 00:00:00 2001 From: Vasyl Zhyliakov Date: Tue, 22 Oct 2024 18:47:12 +0300 Subject: [PATCH 1/5] commit for chat --- README.md | 3 + src/App.tsx | 137 +++++++++++++++++++++++--------- src/api/posts.ts | 20 +++++ src/api/users.ts | 20 +++++ src/components/OnePost.tsx | 41 ++++++++++ src/components/OneUser.tsx | 23 ++++++ src/components/PostsList.tsx | 96 +++++++--------------- src/components/UserSelector.tsx | 51 +++++++----- 8 files changed, 266 insertions(+), 125 deletions(-) create mode 100644 src/api/posts.ts create mode 100644 src/api/users.ts create mode 100644 src/components/OnePost.tsx create mode 100644 src/components/OneUser.tsx diff --git a/README.md b/README.md index c33761fd7..af385c5e3 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,6 @@ Install Prettier Extention and use this [VSCode settings](https://mate-academy.g 1. Implement comment deletion - Delete the commnet immediately not waiting for the server response to improve the UX. 1. (*) Handle `Add` and `Delete` errors so the user can retry + + +[DEMO LINK](https://Vasyl-Zhyliakov.github.io/react_dynamic-list-of-posts/) \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 017957182..1af2627ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import classNames from 'classnames'; +import { useEffect, useState } from 'react'; import 'bulma/css/bulma.css'; import '@fortawesome/fontawesome-free/css/all.css'; @@ -8,53 +9,115 @@ 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 { getUsersFromServer } from './api/users'; +import { Post } from './types/Post'; +import { getPostsFromServer } from './api/posts'; -export const App = () => ( -
-
-
-
-
-
- -
+export const App = () => { + const [users, setUsers] = useState([]); + const [selectedUserId, setSelectedUserId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [posts, setPosts] = useState([]); + const [errorMessage, setErrorMessage] = useState(''); + const [notificationMessage, setNotificationMessage] = useState(''); + const [activePostId, setActivePostId] = useState(null) -
-

No user selected

+ useEffect(() => { + getUsersFromServer() + .then(setUsers) + .catch(() => {}); + }, []); - + useEffect(() => { + if (selectedUserId) { + setIsLoading(true); + setPosts([]); + setErrorMessage(''); + setNotificationMessage(''); -
- Something went wrong! -
+ getPostsFromServer(selectedUserId) + .then((fetchPosts) => { + setPosts(fetchPosts) + if (fetchPosts.length === 0) { + setNotificationMessage('No posts yet') + } + }) + .catch(() => { + setErrorMessage('Something went wrong!'); + }) + .finally(() => { + setIsLoading(false); + }); + } + }, [selectedUserId]); -
- No posts yet + return ( +
+
+
+
+
+
+
- +
+ {!selectedUserId && ( +

No user selected

+ )} + + {isLoading && } + + {errorMessage && !isLoading && ( +
+ {errorMessage} +
+ )} + + {notificationMessage && !isLoading && !errorMessage && ( +
+ {notificationMessage} +
+ )} + + {posts?.length > 0 && + } + + +
-
-
-
- + {activePostId && ( +
+
+ +
+ )}
-
-
-); +
+ ); +}; diff --git a/src/api/posts.ts b/src/api/posts.ts new file mode 100644 index 000000000..4df360122 --- /dev/null +++ b/src/api/posts.ts @@ -0,0 +1,20 @@ +import { Post } from '../types/Post'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 1549; + +export const getPostsFromServer = (userId: number) => { + return client.get(`/posts?userId=${userId}`); +}; + +// export const deleteTodo = (todoId: number) => { +// return client.delete(`/todos/${todoId}`); +// }; + +// export const createTodo = ({ userId, title, completed }: Omit) => { +// return client.post('/todos', { userId, title, completed }); +// }; + +// export const updateTodo = ({ id, title, completed }: Omit) => { +// return client.patch(`/todos/${id}`, { title, completed }); +// }; \ No newline at end of file diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 000000000..4dbeee9cd --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,20 @@ +import { User } from '../types/User'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 1549; + +export const getUsersFromServer = () => { + return client.get(`/users`); +}; + +// export const deleteTodo = (todoId: number) => { +// return client.delete(`/todos/${todoId}`); +// }; + +// export const createTodo = ({ userId, title, completed }: Omit) => { +// return client.post('/todos', { userId, title, completed }); +// }; + +// export const updateTodo = ({ id, title, completed }: Omit) => { +// return client.patch(`/todos/${id}`, { title, completed }); +// }; diff --git a/src/components/OnePost.tsx b/src/components/OnePost.tsx new file mode 100644 index 000000000..2835d2cd3 --- /dev/null +++ b/src/components/OnePost.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import cn from 'classnames'; + +import { Post } from '../types/Post'; + +type Props = { + post: Post; + activePostId: number | null; + setActivePostId: (param: number | null) => void; +}; + +export const OnePost: React.FC = ({ post, activePostId, setActivePostId }) => { + const handleOpenPost = (currentPostId: number | null) => { + if (activePostId === currentPostId) { + setActivePostId(null); + } else { + setActivePostId(currentPostId); + } + }; + + return ( + + {post.id} + + + {post.title} + + + + + + + ); +}; diff --git a/src/components/OneUser.tsx b/src/components/OneUser.tsx new file mode 100644 index 000000000..3f500510c --- /dev/null +++ b/src/components/OneUser.tsx @@ -0,0 +1,23 @@ +import { User } from '../types/User'; + +type Props = { + user: User; + setSelectedUserId: (id: number | null) => void; + setIsDropDownOpen: (status : boolean) => void; +}; + +export const OneUser: React.FC = ({ user, setSelectedUserId, setIsDropDownOpen }) => { + const handleUserSelect = () => { + setSelectedUserId(user.id); + setIsDropDownOpen(false); + } + return ( + + {user.name} + + ); +}; diff --git a/src/components/PostsList.tsx b/src/components/PostsList.tsx index cf90f04b0..1a969ca14 100644 --- a/src/components/PostsList.tsx +++ b/src/components/PostsList.tsx @@ -1,10 +1,27 @@ import React from 'react'; - -export const PostsList: React.FC = () => ( +import cn from 'classnames'; +import { Post } from '../types/Post'; +import { OnePost } from './OnePost'; + +type Props = { + posts: Post[]; + activePostId: number | null; + setActivePostId: (param: number | null) => void; +}; + +export const PostsList: React.FC = ({ + posts, + activePostId, + setActivePostId, +}) => (

Posts:

- +
@@ -15,71 +32,14 @@ export const PostsList: React.FC = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {posts.map(post => ( + + ))} +
#
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 - -
diff --git a/src/components/UserSelector.tsx b/src/components/UserSelector.tsx index c89442841..891fe4ccb 100644 --- a/src/components/UserSelector.tsx +++ b/src/components/UserSelector.tsx @@ -1,17 +1,35 @@ -import React from 'react'; +import { useState } from "react"; +import cn from 'classnames'; + +import { User } from "../types/User"; +import { OneUser } from "./OneUser"; + +type Props = { + users: User[]; + setSelectedUserId: (id: number | null) => void; + selectedUserId: number | null; +}; + +export const UserSelector: React.FC = ({ users, setSelectedUserId, selectedUserId }) => { + const [isDropDownOpen, setIsDropDownOpen] = useState(false); + + const activeUser = users.find(user => user.id === selectedUserId); -export const UserSelector: React.FC = () => { return ( -
+