From ab1b8a643d1a09b2d00e972d0cadb4e6a8bf0462 Mon Sep 17 00:00:00 2001 From: justice chimobi Date: Sun, 6 Oct 2024 09:04:18 +0100 Subject: [PATCH] Feat: follow and unfollow people --- src/Route/index.tsx | 4 + src/api/endpoints/userEndpoints.ts | 10 ++ src/components/ArticleCard/Articles.tsx | 14 +- src/components/FollowCard/index.tsx | 82 +++++++++--- src/components/NavBar/navBarLg.tsx | 22 ++- src/components/NavBar/navBarSm.tsx | 6 +- src/hooks/article/useCreateArticle.ts | 4 +- src/hooks/article/useCreateArticleComment.ts | 8 +- src/hooks/article/useCreateArticleDisLike.ts | 12 +- src/hooks/article/useCreateArticleLike.ts | 12 +- src/hooks/article/useCreateSaveArticles.ts | 12 +- src/hooks/article/useDeleteArticle.ts | 8 +- src/hooks/article/useDeleteSavaArticles.ts | 12 +- src/hooks/article/useEditArticle.ts | 4 +- src/hooks/thread/useCreateThread.ts | 4 +- src/hooks/thread/useCreateThreadComment.ts | 8 +- src/hooks/thread/useCreateThreadDisLike.ts | 12 +- src/hooks/thread/useCreateThreadLike.ts | 12 +- src/hooks/thread/useDeleteThread.ts | 12 +- src/hooks/thread/useEditThread.ts | 4 +- src/hooks/user/useCreateFollowUser.ts | 33 +++++ src/hooks/user/useCreateUnFollowUser.ts | 33 +++++ src/hooks/user/useGetFollowUserArticles.ts | 37 ++++++ src/hooks/user/useGetFollowUsers.ts | 36 +++++ src/pages/Articles/index.tsx | 28 ++-- src/pages/FollowPeople/index.tsx | 125 ++++++++++++++++++ .../Home/Authenticated/Following/index.tsx | 122 ++++++++++++++--- src/pages/Home/Authenticated/ForYou/index.tsx | 26 ++-- src/pages/Threads/index.tsx | 6 +- src/pages/Threads/show/index.tsx | 6 +- .../Users/AuthoredViews/Articles/index.tsx | 2 +- .../Users/AuthoredViews/Threads/index.tsx | 2 +- src/pages/Users/Profile/index.tsx | 2 +- src/pages/Users/show/articles.tsx | 28 ++-- src/pages/Users/show/index.tsx | 71 +++++++--- src/pages/Users/show/thread.tsx | 2 +- src/services/user/index.tsx | 25 ++++ src/types/user/index.ts | 5 +- 38 files changed, 640 insertions(+), 211 deletions(-) create mode 100644 src/hooks/user/useCreateFollowUser.ts create mode 100644 src/hooks/user/useCreateUnFollowUser.ts create mode 100644 src/hooks/user/useGetFollowUserArticles.ts create mode 100644 src/hooks/user/useGetFollowUsers.ts create mode 100644 src/pages/FollowPeople/index.tsx diff --git a/src/Route/index.tsx b/src/Route/index.tsx index 1d0db58..3732aff 100644 --- a/src/Route/index.tsx +++ b/src/Route/index.tsx @@ -29,6 +29,8 @@ const ProfileEdit = lazy(() => import('@pages/Users/Settings')); const ShowUserPublicPosts = lazy(() => import('@pages/Users/show')); const Search = lazy(() => import('@pages/Search')); +const FollowPeople = lazy(() => import('@pages/FollowPeople')) + const Login = lazy(() => import('@pages/Auth/Login')); const Register = lazy(() => import('@pages/Auth/Register')); @@ -39,6 +41,8 @@ const routes = createBrowserRouter( } /> + } /> + } /> } /> diff --git a/src/api/endpoints/userEndpoints.ts b/src/api/endpoints/userEndpoints.ts index b7169c2..abbd6f1 100644 --- a/src/api/endpoints/userEndpoints.ts +++ b/src/api/endpoints/userEndpoints.ts @@ -11,3 +11,13 @@ export const UPDATE_PASSWORD_ENDPOINT = `${API_BASE_URL}/users/accounts/update-p export const DELETE_ACCOUNT_ENDPOINT = `${API_BASE_URL}/users/accounts/delete`; export const PUBLIC_USER_ENDPOINT = `${API_BASE_URL}/users`; + +export const GET_THREE_USERS_ENDPOINT = `${API_BASE_URL}/users/get-three-users`; + +export const GET_ALL_USERS_ENDPOINT = `${API_BASE_URL}/users/all-users`; + +export const Get_FOLLOWING_USERS_ARTICLES_ENDPOINT = `${API_BASE_URL}/users/my-follow-users/articles`; + +export const FOLLOW_USERS_ENDPOINT = `${API_BASE_URL}/users`; + +export const ONFOLLOW_USERS_ENDPOINT = `${API_BASE_URL}/users`; diff --git a/src/components/ArticleCard/Articles.tsx b/src/components/ArticleCard/Articles.tsx index a81d89a..4c327a5 100644 --- a/src/components/ArticleCard/Articles.tsx +++ b/src/components/ArticleCard/Articles.tsx @@ -34,6 +34,8 @@ interface IProps { id?: any; isLoggedIn?: any; is_saved?: boolean; + is_following?: boolean; + followUser?: () => void; saveUnsavedArticle?: () => void; } @@ -51,6 +53,8 @@ const ArticlesCard: FunctionComponent = ({ id, isLoggedIn, is_saved, + is_following, + followUser, saveUnsavedArticle }) => { return ( @@ -146,7 +150,15 @@ const ArticlesCard: FunctionComponent = ({ )} {!isOwner && isLoggedIn && ( - follow + + {is_following ? "following" : "follow"} + )} diff --git a/src/components/FollowCard/index.tsx b/src/components/FollowCard/index.tsx index 04bfb16..c2160b1 100644 --- a/src/components/FollowCard/index.tsx +++ b/src/components/FollowCard/index.tsx @@ -8,15 +8,31 @@ import { CardHeader, Flex, Heading, + Spinner, Text, } from '@chakra-ui/react' import { colors } from '../../colors' import { Button } from '@components/index' -import AvatarPic from '@assets/images/avatar.jpg' +import truncate from '@helpers/truncate' +import { useGetThreeCardUsers } from '@hooks/user/useGetFollowUsers' +import { useUser } from '@context/userContext' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' const FollowCard: FunctionComponent = () => { - const data = [1, 2, 3]; + const { user } = useUser() + const { data: people, isLoading } = useGetThreeCardUsers(); + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); + + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } return ( @@ -25,34 +41,58 @@ const FollowCard: FunctionComponent = () => { - {data.map((index) => ( + {isLoading && ( + + + + )} + {people && people?.map((person: any, index: number) => ( - + + + - Justice Chimobi - Developer at Retailloop + + {person?.fullname} + + {truncate(person?.bio, 25)} - - + {user && ( + + )} + + {!user && ( + + + + )} ))} - - - + { const { user } = useUser(); - const { signOutMutation } = useSignOut() - - const handleLoggedOut = () => { - signOutMutation.mutate(); - }; + const { signOutMutation } = useSignOut(); return ( { > {user?.data?.fullname} - + {isOpen ? : } - - + + Your Profile - + Your Articles - + Your Threads - + Reading List - + Settings - + signOutMutation.mutate()}> Logout diff --git a/src/components/NavBar/navBarSm.tsx b/src/components/NavBar/navBarSm.tsx index 0327380..671f688 100644 --- a/src/components/NavBar/navBarSm.tsx +++ b/src/components/NavBar/navBarSm.tsx @@ -44,10 +44,6 @@ const NavBarSm: FunctionComponent = () => { const { user } = useUser(); const { signOutMutation } = useSignOut() - const handleLoggedOut = () => { - signOutMutation.mutate(); - }; - const handleDrawerBox = () => { setOpen((prevState) => !prevState); } @@ -225,7 +221,7 @@ const NavBarSm: FunctionComponent = () => { - + signOutMutation.mutate()}> Logout diff --git a/src/hooks/article/useCreateArticle.ts b/src/hooks/article/useCreateArticle.ts index 39e83fe..8d1b0b7 100644 --- a/src/hooks/article/useCreateArticle.ts +++ b/src/hooks/article/useCreateArticle.ts @@ -11,9 +11,7 @@ export const useCreateArticle = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['articles'] - }) + queryClient.invalidateQueries({ queryKey: ['articles']}) }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useCreateArticleComment.ts b/src/hooks/article/useCreateArticleComment.ts index e79e5a5..78250e6 100644 --- a/src/hooks/article/useCreateArticleComment.ts +++ b/src/hooks/article/useCreateArticleComment.ts @@ -11,13 +11,9 @@ export const useCreateArticleComment = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles']}); - queryClient.invalidateQueries({ - queryKey: ['article'] - }); + queryClient.invalidateQueries({ queryKey: ['article']}); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useCreateArticleDisLike.ts b/src/hooks/article/useCreateArticleDisLike.ts index f54287d..75e4778 100644 --- a/src/hooks/article/useCreateArticleDisLike.ts +++ b/src/hooks/article/useCreateArticleDisLike.ts @@ -9,17 +9,11 @@ export const useCreateArticleDisLike = () => { const createArticleDisLikeMutation = useMutation({ mutationFn: createArticleDisLike, onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ - queryKey: ['article'] - }); + queryClient.invalidateQueries({ queryKey: ['article'] }); - queryClient.invalidateQueries({ - queryKey: ['public-author-article'] - }); + queryClient.invalidateQueries({ queryKey: ['public-author-article'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useCreateArticleLike.ts b/src/hooks/article/useCreateArticleLike.ts index 04b7fac..b9b5d03 100644 --- a/src/hooks/article/useCreateArticleLike.ts +++ b/src/hooks/article/useCreateArticleLike.ts @@ -9,17 +9,11 @@ export const useCreateArticleLike = () => { const createArticleLikeMutation = useMutation({ mutationFn: createArticleLike, onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ - queryKey: ['article'] - }); + queryClient.invalidateQueries({ queryKey: ['article'] }); - queryClient.invalidateQueries({ - queryKey: ['public-author-article'] - }); + queryClient.invalidateQueries({ queryKey: ['public-author-article'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useCreateSaveArticles.ts b/src/hooks/article/useCreateSaveArticles.ts index 6e65425..21268a2 100644 --- a/src/hooks/article/useCreateSaveArticles.ts +++ b/src/hooks/article/useCreateSaveArticles.ts @@ -11,17 +11,11 @@ export const useCreateSaveArticle = () => { onSuccess: (data) => { successNotification(data?.message) - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ - queryKey: ['saved-articles'] - }); + queryClient.invalidateQueries({ queryKey: ['saved-articles'] }); - queryClient.invalidateQueries({ - queryKey: ['recommented-articles'] - }); + queryClient.invalidateQueries({ queryKey: ['recommented-articles'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useDeleteArticle.ts b/src/hooks/article/useDeleteArticle.ts index e85c270..4d563b8 100644 --- a/src/hooks/article/useDeleteArticle.ts +++ b/src/hooks/article/useDeleteArticle.ts @@ -11,13 +11,9 @@ export const useDeleteArticle = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ - queryKey: ['article'] - }); + queryClient.invalidateQueries({ queryKey: ['article'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useDeleteSavaArticles.ts b/src/hooks/article/useDeleteSavaArticles.ts index 1842542..ced2f0e 100644 --- a/src/hooks/article/useDeleteSavaArticles.ts +++ b/src/hooks/article/useDeleteSavaArticles.ts @@ -11,17 +11,11 @@ export const useDeleteSaveArticle = () => { onSuccess: (data) => { successNotification(data?.message) - queryClient.invalidateQueries({ - queryKey: ['articles'] - }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ - queryKey: ['saved-articles'] - }); + queryClient.invalidateQueries({ queryKey: ['saved-articles'] }); - queryClient.invalidateQueries({ - queryKey: ['recommented-articles'] - }); + queryClient.invalidateQueries({ queryKey: ['recommented-articles'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/article/useEditArticle.ts b/src/hooks/article/useEditArticle.ts index f303d97..49dd851 100644 --- a/src/hooks/article/useEditArticle.ts +++ b/src/hooks/article/useEditArticle.ts @@ -11,9 +11,7 @@ export const useEditArticle = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['articles'] - }) + queryClient.invalidateQueries({ queryKey: ['articles'] }) }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useCreateThread.ts b/src/hooks/thread/useCreateThread.ts index dbb74e3..eab414c 100644 --- a/src/hooks/thread/useCreateThread.ts +++ b/src/hooks/thread/useCreateThread.ts @@ -11,9 +11,7 @@ export const useCreateThread = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['threads'] - }) + queryClient.invalidateQueries({ queryKey: ['threads'] }) }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useCreateThreadComment.ts b/src/hooks/thread/useCreateThreadComment.ts index de4f3f0..a6db9fc 100644 --- a/src/hooks/thread/useCreateThreadComment.ts +++ b/src/hooks/thread/useCreateThreadComment.ts @@ -11,13 +11,9 @@ export const useCreateThreadComment = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['threads'] - }); + queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ - queryKey: ['thread'] - }) + queryClient.invalidateQueries({ queryKey: ['thread'] }) }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useCreateThreadDisLike.ts b/src/hooks/thread/useCreateThreadDisLike.ts index 25bd8a9..21b62c1 100644 --- a/src/hooks/thread/useCreateThreadDisLike.ts +++ b/src/hooks/thread/useCreateThreadDisLike.ts @@ -9,17 +9,11 @@ export const useCreateThreadDisLike = () => { const createThreadDisLikeMutation = useMutation({ mutationFn: createThreadDisLike, onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['threads'] - }); + queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ - queryKey: ['thread'] - }); + queryClient.invalidateQueries({ queryKey: ['thread'] }); - queryClient.invalidateQueries({ - queryKey: ['public-author-threads'] - }); + queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useCreateThreadLike.ts b/src/hooks/thread/useCreateThreadLike.ts index 8c2f207..48442d3 100644 --- a/src/hooks/thread/useCreateThreadLike.ts +++ b/src/hooks/thread/useCreateThreadLike.ts @@ -9,17 +9,11 @@ export const useCreateThreadLike = () => { const createThreadLikeMutation = useMutation({ mutationFn: createThreadLike, onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['threads'] - }); + queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ - queryKey: ['thread'] - }); + queryClient.invalidateQueries({ queryKey: ['thread'] }); - queryClient.invalidateQueries({ - queryKey: ['public-author-threads'] - }); + queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useDeleteThread.ts b/src/hooks/thread/useDeleteThread.ts index 8aabd97..f085735 100644 --- a/src/hooks/thread/useDeleteThread.ts +++ b/src/hooks/thread/useDeleteThread.ts @@ -11,17 +11,11 @@ export const useDeleteThread = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['threads'] - }) + queryClient.invalidateQueries({ queryKey: ['threads'] }) - queryClient.invalidateQueries({ - queryKey: ['thread'] - }) + queryClient.invalidateQueries({ queryKey: ['thread'] }) - queryClient.invalidateQueries({ - queryKey: ['public-author-threads'] - }); + queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/thread/useEditThread.ts b/src/hooks/thread/useEditThread.ts index 4569adb..509eb17 100644 --- a/src/hooks/thread/useEditThread.ts +++ b/src/hooks/thread/useEditThread.ts @@ -11,9 +11,7 @@ export const useEditThread = () => { onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['threads'] - }) + queryClient.invalidateQueries({ queryKey: ['threads'] }) }, onError: (error: any) => { errorNotification(error?.response?.data?.message) diff --git a/src/hooks/user/useCreateFollowUser.ts b/src/hooks/user/useCreateFollowUser.ts new file mode 100644 index 0000000..fa3b068 --- /dev/null +++ b/src/hooks/user/useCreateFollowUser.ts @@ -0,0 +1,33 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { createFollowUser } from '@services/user' + +export const useCreateFollowUser = () => { + const queryClient = useQueryClient(); + + const createFollowUserMutation = useMutation({ + mutationFn: createFollowUser, + onSuccess: (data) => { + successNotification(data.message); + queryClient.invalidateQueries({ queryKey: ['follow-users'] }); + + queryClient.invalidateQueries({ queryKey: ['limit-follow-users'] }); + + queryClient.invalidateQueries({ queryKey: ['user'] }); + + queryClient.invalidateQueries({ queryKey: ['articles'] }); + + queryClient.invalidateQueries({ queryKey: ['public-author-articles'] }); + + queryClient.invalidateQueries({ queryKey: ['recommented-articles'] }); + + queryClient.invalidateQueries({ queryKey: ['get-followed-users-articles'] }); + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message) + } + }) + + return { createFollowUserMutation } +} \ No newline at end of file diff --git a/src/hooks/user/useCreateUnFollowUser.ts b/src/hooks/user/useCreateUnFollowUser.ts new file mode 100644 index 0000000..2e1922b --- /dev/null +++ b/src/hooks/user/useCreateUnFollowUser.ts @@ -0,0 +1,33 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { createOnFollowUser } from '@services/user' + +export const useCreateOnFollowUser = () => { + const queryClient = useQueryClient(); + + const createOnFollowUserMutation = useMutation({ + mutationFn: createOnFollowUser, + onSuccess: (data) => { + successNotification(data.message); + queryClient.invalidateQueries({ queryKey: ['follow-users'] }); + + queryClient.invalidateQueries({ queryKey: ['limit-follow-users'] }); + + queryClient.invalidateQueries({ queryKey: ['user'] }); + + queryClient.invalidateQueries({ queryKey: ['articles'] }); + + queryClient.invalidateQueries({ queryKey: ['public-author-articles'] }); + + queryClient.invalidateQueries({ queryKey: ['recommented-articles'] }); + + queryClient.invalidateQueries({ queryKey: ['get-followed-users-articles'] }); + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message) + } + }) + + return { createOnFollowUserMutation } +} \ No newline at end of file diff --git a/src/hooks/user/useGetFollowUserArticles.ts b/src/hooks/user/useGetFollowUserArticles.ts new file mode 100644 index 0000000..5f88b16 --- /dev/null +++ b/src/hooks/user/useGetFollowUserArticles.ts @@ -0,0 +1,37 @@ +import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query' + +import { axiosInstance } from '@api/axiosInstance' +import { Get_FOLLOWING_USERS_ARTICLES_ENDPOINT } from '@api/index' + +export const useGetFollowUsersArticles = (limit: number = 0) => { + const fetchPaginatedArticles = async ({ pageParam = 0 }) => { + const res = await axiosInstance.get(`${Get_FOLLOWING_USERS_ARTICLES_ENDPOINT}?limit=${limit}&page=${pageParam}`) + + const dataResponse = await res.data; + return { ...dataResponse, prevOffset: pageParam }; + }; + + const { + data: followingUsersArticlesResponse, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isSuccess + } = useInfiniteQuery({ + queryKey: ['get-followed-users-articles', limit], + queryFn: fetchPaginatedArticles, + placeholderData: keepPreviousData, + initialPageParam: 1, + getNextPageParam: (lastPage) => { + if (!lastPage?.data?.pagination?.next_page_url) { + return undefined; + } + return lastPage?.data?.pagination?.current_page + 1 + } + }) + + const articles = followingUsersArticlesResponse?.pages ?? null; + + return { articles, isLoading, isSuccess, fetchNextPage, hasNextPage, isFetchingNextPage } +} \ No newline at end of file diff --git a/src/hooks/user/useGetFollowUsers.ts b/src/hooks/user/useGetFollowUsers.ts new file mode 100644 index 0000000..90ddd45 --- /dev/null +++ b/src/hooks/user/useGetFollowUsers.ts @@ -0,0 +1,36 @@ +import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query' + +import { getAllFollowUsers, getThreeUsersOnCard } from '@services/user' + +export const useGetThreeCardUsers = () => { + return useQuery({ + queryKey: ['limit-follow-users'], + queryFn: getThreeUsersOnCard, + }) +} + +export const useGetAllFollowUsers = (limit: number = 0) => { + const { + data: peopleResponse, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isSuccess + } = useInfiniteQuery({ + queryKey: ['follow-users'], + queryFn: ({ pageParam = 0 }) => getAllFollowUsers(limit, pageParam), + placeholderData: keepPreviousData, + initialPageParam: 1, + getNextPageParam: (lastPage) => { + if (!lastPage?.data?.pagination?.next_page_url) { + return undefined + } + return lastPage?.data?.pagination?.current_page + 1 + } + }) + + const people = peopleResponse?.pages ?? null; + + return { people, isLoading, isSuccess, fetchNextPage, isFetchingNextPage, hasNextPage } +} \ No newline at end of file diff --git a/src/pages/Articles/index.tsx b/src/pages/Articles/index.tsx index 7e4a435..2d8cd2c 100644 --- a/src/pages/Articles/index.tsx +++ b/src/pages/Articles/index.tsx @@ -18,6 +18,8 @@ import { useDeleteArticle } from '@hooks/article/useDeleteArticle' import { useGetPinnedArticles } from '@hooks/article/useGetPinnedArticles' import { useCreateSaveArticle } from '@hooks/article/useCreateSaveArticles' import { useDeleteSaveArticle } from '@hooks/article/useDeleteSavaArticles' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' const Articles: FunctionComponent = () => { const { user } = useUser(); @@ -28,11 +30,13 @@ const Articles: FunctionComponent = () => { hasNextPage, fetchNextPage, isFetchingNextPage - } = useGetPaginatedArticles(10) + } = useGetPaginatedArticles(25) const { deleteArticleMutation } = useDeleteArticle() const { data: pinArticles } = useGetPinnedArticles(); const { createSaveArticleMutation } = useCreateSaveArticle(); - const { deleteSaveArticleMutation } = useDeleteSaveArticle() + const { deleteSaveArticleMutation } = useDeleteSaveArticle(); + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); const { isOpen, onOpen, onClose } = useDisclosure(); const [deleteArticleId, setDeleteArticleId] = useState(null); @@ -49,19 +53,19 @@ const Articles: FunctionComponent = () => { const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { if (is_saved) { - unsaveArticle(articleId); + deleteSaveArticleMutation.mutate(articleId); } else { - saveArticle(articleId); + createSaveArticleMutation.mutate(articleId); } } - const saveArticle = (articleId: string) => { - createSaveArticleMutation.mutate(articleId); - }; - - const unsaveArticle = (articleId: string) => { - deleteSaveArticleMutation.mutate(articleId); - }; + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } return ( <> @@ -148,6 +152,8 @@ const Articles: FunctionComponent = () => { onDelete={() => handleDelete(article?.id)} isLoggedIn={!!user} is_saved={article?.is_saved} + is_following={article?.author?.is_following} + followUser={() => handleFollowUnfollow(article?.author?.id, article?.author?.is_following)} saveUnsavedArticle={() => handleSaveUnsavedArticle(article?.id, article?.is_saved)} /> ))} diff --git a/src/pages/FollowPeople/index.tsx b/src/pages/FollowPeople/index.tsx new file mode 100644 index 0000000..b8ea4ad --- /dev/null +++ b/src/pages/FollowPeople/index.tsx @@ -0,0 +1,125 @@ +import { Fragment } from 'react' +import { Link } from 'react-router-dom' +import { Avatar, Box, Card, CardBody, CardHeader, Flex, Heading, Spacer, Spinner, Text } from '@chakra-ui/react' + +import { Button } from '@components/index' +import { useUser } from '@context/userContext' +import truncate from '@helpers/truncate' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' +import { useGetAllFollowUsers } from '@hooks/user/useGetFollowUsers' +import { colors } from '../../colors' +import { Helmet } from 'react-helmet-async' + +const FollowPeople = () => { + const { user } = useUser() + const { people, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } = useGetAllFollowUsers(15); + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); + + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } + + return ( + <> + + People to follow | learn-hub + + + + + + + People to follow + + + + {isLoading && ( + + + + )} + {people && people?.map((page: any, index: number) => ( + + {page?.data?.users?.map((person: any, index: number) => ( + + + + + + + + + {person?.fullname} + + + + + + {user && ( + + )} + + {!user && ( + + + + )} + + + + + {truncate(person?.bio, 150)} + + + ))} + + ))} + + {hasNextPage && ( + + + + )} + + + + + ) +} + +export default FollowPeople \ No newline at end of file diff --git a/src/pages/Home/Authenticated/Following/index.tsx b/src/pages/Home/Authenticated/Following/index.tsx index e0f1ca0..49bc3f9 100644 --- a/src/pages/Home/Authenticated/Following/index.tsx +++ b/src/pages/Home/Authenticated/Following/index.tsx @@ -1,29 +1,117 @@ -import { FunctionComponent } from 'react' +import { Fragment, FunctionComponent } from 'react' import { Box, Heading } from '@chakra-ui/react' -import { Button } from '@components/index' +import { ArticlesCard, Button, Skeleton } from '@components/index' +import { useGetFollowUsersArticles } from '@hooks/user/useGetFollowUserArticles' +import { useUser } from '@context/userContext' +import { useCreateSaveArticle } from '@hooks/article/useCreateSaveArticles' +import { useDeleteSaveArticle } from '@hooks/article/useDeleteSavaArticles' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' interface FollowingProps { setTabIndex: (index: number) => void; } const Following: FunctionComponent = ({ setTabIndex }) => { + const { user } = useUser(); + const { + articles, + isSuccess, + isLoading, + hasNextPage, + isFetchingNextPage, + fetchNextPage + } = useGetFollowUsersArticles(25); + const { createSaveArticleMutation } = useCreateSaveArticle(); + const { deleteSaveArticleMutation } = useDeleteSaveArticle(); + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); + console.log(articles); + + const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { + if (is_saved) { + deleteSaveArticleMutation.mutate(articleId); + } else { + createSaveArticleMutation.mutate(articleId); + } + } + + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } + return ( - - Stories from the authors you follow will appear here. - - - - - + <> + {isLoading && } + + {articles && isSuccess && articles?.map((page: any, pageIndex: number) => ( + + {page?.data?.articles.map((article: any, index: number) => ( + handleFollowUnfollow(article?.author?.id, article?.author?.is_following)} + saveUnsavedArticle={() => handleSaveUnsavedArticle(article?.id, article?.is_saved)} + /> + ))} + + {page?.data?.articles?.length === 0 && ( + + Stories from the authors you follow will appear here. + + + + + + )} + + ))} + + {hasNextPage && ( + + + + )} + ) } diff --git a/src/pages/Home/Authenticated/ForYou/index.tsx b/src/pages/Home/Authenticated/ForYou/index.tsx index 61701fd..5ffc156 100644 --- a/src/pages/Home/Authenticated/ForYou/index.tsx +++ b/src/pages/Home/Authenticated/ForYou/index.tsx @@ -7,6 +7,8 @@ import { useDeleteArticle } from '@hooks/article/useDeleteArticle' import { useGetRecommentedArticles } from '@hooks/article/useGetRecommentedArticles' import { useCreateSaveArticle } from '@hooks/article/useCreateSaveArticles' import { useDeleteSaveArticle } from '@hooks/article/useDeleteSavaArticles' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' const ForYou = () => { const { user } = useUser(); @@ -20,7 +22,9 @@ const ForYou = () => { } = useGetRecommentedArticles(20) const { deleteArticleMutation } = useDeleteArticle() const { createSaveArticleMutation } = useCreateSaveArticle(); - const { deleteSaveArticleMutation} = useDeleteSaveArticle() + const { deleteSaveArticleMutation} = useDeleteSaveArticle(); + const { createFollowUserMutation } = useCreateFollowUser(); + const { createOnFollowUserMutation } = useCreateOnFollowUser(); const { isOpen, onOpen, onClose } = useDisclosure(); const [deleteArticleId, setDeleteArticleId] = useState(null); @@ -37,19 +41,19 @@ const ForYou = () => { const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { if (is_saved) { - unsaveArticle(articleId); + deleteSaveArticleMutation.mutate(articleId); } else { - saveArticle(articleId); + createSaveArticleMutation.mutate(articleId); } } - const saveArticle = (articleId: string) => { - createSaveArticleMutation.mutate(articleId); - }; - - const unsaveArticle = (articleId: string) => { - deleteSaveArticleMutation.mutate(articleId); - }; + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } return ( <> @@ -73,6 +77,8 @@ const ForYou = () => { onDelete={() => handleDelete(article?.id)} isLoggedIn={!!user} is_saved={article?.is_saved} + is_following={article?.author?.is_following} + followUser={() => handleFollowUnfollow(article?.author?.id, article?.author?.is_following)} saveUnsavedArticle={() => handleSaveUnsavedArticle(article?.id, article?.is_saved)} /> ))} diff --git a/src/pages/Threads/index.tsx b/src/pages/Threads/index.tsx index 74ea07a..67a741e 100644 --- a/src/pages/Threads/index.tsx +++ b/src/pages/Threads/index.tsx @@ -27,7 +27,7 @@ const Threads: FunctionComponent = () => { hasNextPage, fetchNextPage, isFetchingNextPage - } = useGetPaginatedThreads(10) + } = useGetPaginatedThreads(25) return ( <> @@ -40,7 +40,7 @@ const Threads: FunctionComponent = () => { width={"90%"} m={"4rem auto"} > - Forum + Discussions {user && ( @@ -131,7 +131,7 @@ const Threads: FunctionComponent = () => { */} - + diff --git a/src/pages/Threads/show/index.tsx b/src/pages/Threads/show/index.tsx index 9e8da88..a0d4fc3 100644 --- a/src/pages/Threads/show/index.tsx +++ b/src/pages/Threads/show/index.tsx @@ -24,7 +24,7 @@ const ShowThread: FunctionComponent = () => { const { data, isLoading, isSuccess, error } = useGetSingleThread(id!); - const truncateLenght = useBreakpointValue({base: 45, md: 30, lg: 75}); + const truncateLenght = useBreakpointValue({base: 45, md: 30, lg: 70}); if (error) return ; @@ -47,7 +47,7 @@ const ShowThread: FunctionComponent = () => { size={"md"} alignItems={"center"} > - Forum + Discussions {" > "} {truncate(data?.data?.title, truncateLenght)} @@ -90,7 +90,7 @@ const ShowThread: FunctionComponent = () => { */} - + diff --git a/src/pages/Users/AuthoredViews/Articles/index.tsx b/src/pages/Users/AuthoredViews/Articles/index.tsx index f54062e..6526543 100644 --- a/src/pages/Users/AuthoredViews/Articles/index.tsx +++ b/src/pages/Users/AuthoredViews/Articles/index.tsx @@ -23,7 +23,7 @@ const ArthoredArticles: FunctionComponent = () => { fetchNextPage, isFetchingNextPage, dataStatus - } = useGetAuthoredArticles(10, username!) + } = useGetAuthoredArticles(25, username!) const { deleteArticleMutation } = useDeleteArticle() const { isOpen, onOpen, onClose } = useDisclosure(); const [deletingArticleId, setDeletingArticleId] = useState(null); diff --git a/src/pages/Users/AuthoredViews/Threads/index.tsx b/src/pages/Users/AuthoredViews/Threads/index.tsx index 75590de..8991017 100644 --- a/src/pages/Users/AuthoredViews/Threads/index.tsx +++ b/src/pages/Users/AuthoredViews/Threads/index.tsx @@ -31,7 +31,7 @@ const ArthoredThreads: FunctionComponent = () => { fetchNextPage, isFetchingNextPage, dataStatus - } = useGetAuthoredThreads(10, username!) + } = useGetAuthoredThreads(25, username!) const { deleteThreadMutation } = useDeleteThread(); const { isOpen, onOpen, onClose } = useDisclosure(); diff --git a/src/pages/Users/Profile/index.tsx b/src/pages/Users/Profile/index.tsx index 17b1913..8ddd82a 100644 --- a/src/pages/Users/Profile/index.tsx +++ b/src/pages/Users/Profile/index.tsx @@ -78,7 +78,7 @@ const Profile: FunctionComponent = () => { {user?.data?.fullname} {user?.data?.profile_headlines} {user?.data?.state}, {user?.data?.country} - 0 followers - 0 following + {user?.data?.followers} followers - {user?.data?.followings} following { const { username } = useParams(); @@ -20,10 +22,12 @@ const PublicUserArticles: FunctionComponent = () => { hasNextPage, fetchNextPage, isFetchingNextPage - } = useGetPublicAuthoredArticles(20, username!); + } = useGetPublicAuthoredArticles(25, username!); const { deleteArticleMutation } = useDeleteArticle() const { createSaveArticleMutation } = useCreateSaveArticle(); - const { deleteSaveArticleMutation } = useDeleteSaveArticle() + const { deleteSaveArticleMutation } = useDeleteSaveArticle(); + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); const { isOpen, onOpen, onClose } = useDisclosure(); const [deleteArticleId, setDeleteArticleId] = useState(null); @@ -40,19 +44,19 @@ const PublicUserArticles: FunctionComponent = () => { const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { if (is_saved) { - unsaveArticle(articleId); + deleteSaveArticleMutation.mutate(articleId); } else { - saveArticle(articleId); + createSaveArticleMutation.mutate(articleId); } } - const saveArticle = (articleId: string) => { - createSaveArticleMutation.mutate(articleId); - }; - - const unsaveArticle = (articleId: string) => { - deleteSaveArticleMutation.mutate(articleId); - }; + const handleFollowUnfollow = (userId: string, following: boolean) => { + if (following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } return ( <> @@ -76,6 +80,8 @@ const PublicUserArticles: FunctionComponent = () => { onDelete={() => handleDelete(article?.id)} isLoggedIn={!!user} is_saved={article?.is_saved} + is_following={article?.author?.is_following} + followUser={() => handleFollowUnfollow(article?.author?.id, article?.author?.is_following)} saveUnsavedArticle={() => handleSaveUnsavedArticle(article?.id, article?.is_saved)} /> ))} diff --git a/src/pages/Users/show/index.tsx b/src/pages/Users/show/index.tsx index 2737f48..8673fd9 100644 --- a/src/pages/Users/show/index.tsx +++ b/src/pages/Users/show/index.tsx @@ -1,5 +1,5 @@ import { FunctionComponent } from 'react' -import { useParams } from 'react-router-dom' +import { Link, useParams } from 'react-router-dom' import { Avatar, Box, @@ -19,15 +19,27 @@ import PublicUserArticles from '@pages/Users/show/articles' import PublicUserThreads from '@pages/Users/show/thread' import { useGetPublicUser } from '@hooks/user/useGetPublicUser' import PublicUserAboutDetails from '@pages/Users/show/about' -import { NotFound, Skeleton } from '@components/index' +import { Button, FollowCard, NotFound, Skeleton } from '@components/index' import { Helmet } from 'react-helmet-async' -// import FollowCard from '@components/FollowCard' +import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' +import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' +import { useUser } from '@context/userContext' const ShowUserPublicPosts: FunctionComponent = () => { const { username } = useParams(); + const { user } = useUser() const { data, isLoading } = useGetPublicUser(username!) + const { createFollowUserMutation } = useCreateFollowUser() + const { createOnFollowUserMutation } = useCreateOnFollowUser(); + const handleFollowUnfollow = (userId: string, is_following: boolean|undefined) => { + if (is_following) { + createOnFollowUserMutation.mutate(userId) + } else { + createFollowUserMutation.mutate(userId) + } + } if (isLoading) return @@ -35,6 +47,8 @@ const ShowUserPublicPosts: FunctionComponent = () => { const userData = data?.data; + const isOwner = user?.data?.username === userData?.username; + return ( <> @@ -90,25 +104,46 @@ const ShowUserPublicPosts: FunctionComponent = () => { /> {userData?.fullname} - {/* 0 Follower */} + + {userData?.followers} {userData?.followers === 1 ? 'Follower' : 'Followers'} + + {userData?.profile_headlines} - {/* - - */} - - {/* - - */} + {!isOwner && ( + + {user && ( + + )} + + {!user && ( + + + + )} + + )} + + + + diff --git a/src/pages/Users/show/thread.tsx b/src/pages/Users/show/thread.tsx index cba8a50..6a7b161 100644 --- a/src/pages/Users/show/thread.tsx +++ b/src/pages/Users/show/thread.tsx @@ -15,7 +15,7 @@ const PublicUserThreads: FunctionComponent = () => { hasNextPage, fetchNextPage, isFetchingNextPage - } = useGetPublicAuthoredThreads(20, username!); + } = useGetPublicAuthoredThreads(25, username!); return ( <> diff --git a/src/services/user/index.tsx b/src/services/user/index.tsx index 33ee837..9e13dbb 100644 --- a/src/services/user/index.tsx +++ b/src/services/user/index.tsx @@ -1,6 +1,10 @@ import { axiosInstance } from '@api/axiosInstance' import { DELETE_ACCOUNT_ENDPOINT, + FOLLOW_USERS_ENDPOINT, + GET_ALL_USERS_ENDPOINT, + GET_THREE_USERS_ENDPOINT, + ONFOLLOW_USERS_ENDPOINT, PUBLIC_USER_ENDPOINT, UPDATE_PASSWORD_ENDPOINT, UPDATE_PROFILE_ENDPOINT, @@ -43,4 +47,25 @@ export const deleteAccount = async () => { export const getPublicUser = async (username: string): Promise => { const response = await axiosInstance.get(`${PUBLIC_USER_ENDPOINT}/${username}`); return response.data; +} + +export const getThreeUsersOnCard = async () => { + const response = await axiosInstance.get(GET_THREE_USERS_ENDPOINT); + return response.data.data; +} + +export const getAllFollowUsers = async (limit: number, page: number) => { + const response = await axiosInstance.get(`${GET_ALL_USERS_ENDPOINT}?limit=${limit}&page=${page}`); + const dataResponse = await response.data; + return {...dataResponse, prevOffset: page} +} + +export const createFollowUser = async (id: string) => { + const response = await axiosInstance.post(`${FOLLOW_USERS_ENDPOINT}/${id}/follow`); + return response.data; +} + +export const createOnFollowUser = async (id: string) => { + const response = await axiosInstance.post(`${ONFOLLOW_USERS_ENDPOINT}/${id}/unfollow`); + return response.data; } \ No newline at end of file diff --git a/src/types/user/index.ts b/src/types/user/index.ts index 411ff33..83c13d8 100644 --- a/src/types/user/index.ts +++ b/src/types/user/index.ts @@ -1,5 +1,5 @@ export interface IUserProps { - id: string | number; + id: string; fullname: string; username: string; email: string; @@ -11,6 +11,9 @@ export interface IUserProps { state: string; country: string; bio: string; + followers?: number; + followings?: number; + is_following?: boolean; } export interface IUser {