From 9994a041d238cfc0184689721bc16a0967c84e28 Mon Sep 17 00:00:00 2001 From: justice chimobi Date: Sun, 13 Oct 2024 00:45:43 +0100 Subject: [PATCH] Text to speech to articles --- package-lock.json | 51 +++- package.json | 1 + src/components/ArticleCard/Articles.tsx | 14 +- src/components/NavBar/navBarLg.tsx | 19 +- src/components/NavBar/navBarSm.tsx | 1 + src/components/ShowLoginModal/index.tsx | 6 +- src/components/TextSpeech/index.tsx | 80 ++++++ src/components/index.tsx | 2 + src/hooks/article/useCreateArticleComment.ts | 1 - src/hooks/article/useCreateArticleDisLike.ts | 2 - src/hooks/article/useCreateArticleLike.ts | 2 - src/hooks/article/useCreateSaveArticles.ts | 3 +- src/hooks/article/useDeleteArticle.ts | 1 - src/hooks/article/useDeleteSavaArticles.ts | 2 + src/hooks/auth/useSignOut.ts | 4 +- src/hooks/thread/useCreateThreadComment.ts | 1 - src/hooks/thread/useCreateThreadDisLike.ts | 2 - src/hooks/thread/useCreateThreadLike.ts | 2 - src/hooks/thread/useDeleteThread.ts | 2 - src/hooks/user/useCreateFollowUser.ts | 7 +- src/hooks/user/useCreateUnFollowUser.ts | 7 +- src/hooks/user/useDeleteAccount.ts | 4 +- src/hooks/user/useUpdateAvatar.ts | 4 +- src/hooks/user/useUpdateProfile.ts | 4 +- src/pages/Articles/HeroSection/index.tsx | 78 +++--- .../components/ArticleActionButtons.tsx | 184 +++++++------ src/pages/Articles/components/ArticleForm.tsx | 20 +- src/pages/Articles/index.tsx | 37 +-- src/pages/Articles/show/index.tsx | 248 ++++++++---------- .../Home/Authenticated/Following/index.tsx | 28 +- src/pages/Home/Authenticated/ForYou/index.tsx | 29 +- src/pages/Home/Authenticated/index.tsx | 11 +- src/pages/Home/Public/Articles/index.tsx | 2 +- src/pages/Home/Public/Threads/index.tsx | 5 +- src/pages/Search/articles.tsx | 27 +- src/pages/Search/threads.tsx | 28 +- src/pages/Search/users.tsx | 26 +- src/pages/Threads/index.tsx | 11 +- .../Users/AuthoredViews/Articles/index.tsx | 12 +- src/pages/Users/Profile/index.tsx | 9 +- src/pages/Users/Settings/index.tsx | 70 +++-- src/types/article/index.ts | 6 + 42 files changed, 523 insertions(+), 530 deletions(-) create mode 100644 src/components/TextSpeech/index.tsx diff --git a/package-lock.json b/package-lock.json index 1ec14f0..7d4bb11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.11.5", "@fontsource/open-sans": "^5.1.0", "@fontsource/raleway": "^5.1.0", + "@phntms/react-share": "^1.0.2-rc1", "@tanstack/react-query": "^5.50.1", "@tanstack/react-query-devtools": "^5.56.2", "axios": "^1.7.2", @@ -220,10 +221,11 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2319,6 +2321,19 @@ "node": ">= 8" } }, + "node_modules/@phntms/react-share": { + "version": "1.0.2-rc1", + "resolved": "https://registry.npmjs.org/@phntms/react-share/-/react-share-1.0.2-rc1.tgz", + "integrity": "sha512-XOLglXQyfXAfDUTt+wD9zbOhdxUimNyomkg4WSNnoIQ7zLrkfjgfmrcpyegIQ7EzLNxo5IQ2xXFXHQ416iRngQ==", + "license": "MIT", + "dependencies": { + "is-absolute-url": "^3.0.3" + }, + "peerDependencies": { + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2666,6 +2681,18 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -4315,6 +4342,15 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -5558,6 +5594,15 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", diff --git a/package.json b/package.json index a9412cd..f117579 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.11.5", "@fontsource/open-sans": "^5.1.0", "@fontsource/raleway": "^5.1.0", + "@phntms/react-share": "^1.0.2-rc1", "@tanstack/react-query": "^5.50.1", "@tanstack/react-query-devtools": "^5.56.2", "axios": "^1.7.2", diff --git a/src/components/ArticleCard/Articles.tsx b/src/components/ArticleCard/Articles.tsx index 4c327a5..2abd93c 100644 --- a/src/components/ArticleCard/Articles.tsx +++ b/src/components/ArticleCard/Articles.tsx @@ -1,16 +1,6 @@ import { FunctionComponent } from 'react' import { Link } from 'react-router-dom' -import { - Avatar, - Box, - Card, - CardBody, - Flex, - Heading, - Image, - Text, - Tooltip -} from '@chakra-ui/react' +import { Avatar, Box, Card, CardBody, Flex, Heading, Image, Text, Tooltip } from '@chakra-ui/react' import { CiEdit } from 'react-icons/ci' import { MdDeleteOutline } from 'react-icons/md' import { BsSave } from 'react-icons/bs' @@ -157,7 +147,7 @@ const ArticlesCard: FunctionComponent = ({ _hover={{ textDecoration: "underline" }} onClick={followUser} > - {is_following ? "following" : "follow"} + {is_following ? "unfollow" : "follow"} )} diff --git a/src/components/NavBar/navBarLg.tsx b/src/components/NavBar/navBarLg.tsx index 32e00f8..8aff830 100644 --- a/src/components/NavBar/navBarLg.tsx +++ b/src/components/NavBar/navBarLg.tsx @@ -37,6 +37,7 @@ const NavBarLg: FunctionComponent = () => { as="nav" bg={colors.secondary} display={{ base: 'none', md: 'flex' }} + borderBottom={"2px solid #f1f1f1"} > { {user ? ( - - + + {({ isOpen }) => ( <> - + { > {user?.data?.fullname} - + {isOpen ? : } - + Your Profile - + Your Articles - + Your Threads - + Reading List - + Settings diff --git a/src/components/NavBar/navBarSm.tsx b/src/components/NavBar/navBarSm.tsx index 671f688..9d7ed90 100644 --- a/src/components/NavBar/navBarSm.tsx +++ b/src/components/NavBar/navBarSm.tsx @@ -58,6 +58,7 @@ const NavBarSm: FunctionComponent = () => { p="8px" w="100%" display={{ base: 'flex', md: 'none' }} + borderBottom={"2px solid #f1f1f1"} > = ({ isOpen, onClose }) => { /> - Sign in to paticipate + Log In + {({ handleSubmit, errors, touched }) => ( = ({ title, content }) => { + const [isSpeaking, setIsSpeaking] = useState(false); + const [isPaused, setIsPaused] = useState(false); + const [utterance, setUtterance] = useState(null); + + const synth = window.speechSynthesis; + + useEffect(() => { + const newUtterance = new SpeechSynthesisUtterance(`${title} ${content}`); + newUtterance.lang = "en-US"; + setUtterance(newUtterance); + + return () => { + synth.cancel(); + } + }, [title, content]); + + + const handlePlay = () => { + if (utterance && !isSpeaking) { + synth.speak(utterance); + setIsSpeaking(true); + setIsPaused(false); + } else if (isPaused) { + synth.resume(); + setIsPaused(false); + } + } + + const handlePause = () => { + if (synth.speaking && !isPaused) { + synth.pause(); + setIsPaused(true); + } + } + + const handleStop = () => { + synth.cancel(); + setIsSpeaking(false); + setIsPaused(false); + } + + return ( + + {!isSpeaking || isPaused ? ( + + + + + + ): ( + + + + + + )} + + {isSpeaking && !isPaused && ( + + + + + + + )} + + ) +} + +export default TextSpeech; \ No newline at end of file diff --git a/src/components/index.tsx b/src/components/index.tsx index 779a415..4e2032c 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -20,6 +20,7 @@ import Loading from '@components/Loading' import LoginForm from '@components/LoginForm' import RegisterForm from '@components/RegisterForm' import ShowLoginModal from '@components/ShowLoginModal' +import TextSpeech from '@components/TextSpeech' export { Alert, @@ -44,4 +45,5 @@ export { LoginForm, RegisterForm, ShowLoginModal, + TextSpeech, } \ No newline at end of file diff --git a/src/hooks/article/useCreateArticleComment.ts b/src/hooks/article/useCreateArticleComment.ts index 78250e6..9fb9b10 100644 --- a/src/hooks/article/useCreateArticleComment.ts +++ b/src/hooks/article/useCreateArticleComment.ts @@ -12,7 +12,6 @@ export const useCreateArticleComment = () => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['articles']}); - queryClient.invalidateQueries({ queryKey: ['article']}); }, onError: (error: any) => { diff --git a/src/hooks/article/useCreateArticleDisLike.ts b/src/hooks/article/useCreateArticleDisLike.ts index 75e4778..074a7ed 100644 --- a/src/hooks/article/useCreateArticleDisLike.ts +++ b/src/hooks/article/useCreateArticleDisLike.ts @@ -10,9 +10,7 @@ export const useCreateArticleDisLike = () => { mutationFn: createArticleDisLike, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ queryKey: ['article'] }); - queryClient.invalidateQueries({ queryKey: ['public-author-article'] }); }, onError: (error: any) => { diff --git a/src/hooks/article/useCreateArticleLike.ts b/src/hooks/article/useCreateArticleLike.ts index b9b5d03..f73f592 100644 --- a/src/hooks/article/useCreateArticleLike.ts +++ b/src/hooks/article/useCreateArticleLike.ts @@ -10,9 +10,7 @@ export const useCreateArticleLike = () => { mutationFn: createArticleLike, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ queryKey: ['article'] }); - queryClient.invalidateQueries({ queryKey: ['public-author-article'] }); }, onError: (error: any) => { diff --git a/src/hooks/article/useCreateSaveArticles.ts b/src/hooks/article/useCreateSaveArticles.ts index 21268a2..1776fc6 100644 --- a/src/hooks/article/useCreateSaveArticles.ts +++ b/src/hooks/article/useCreateSaveArticles.ts @@ -11,10 +11,9 @@ export const useCreateSaveArticle = () => { onSuccess: (data) => { successNotification(data?.message) + queryClient.invalidateQueries({ queryKey: ['article'] }); queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ queryKey: ['saved-articles'] }); - queryClient.invalidateQueries({ queryKey: ['recommented-articles'] }); }, onError: (error: any) => { diff --git a/src/hooks/article/useDeleteArticle.ts b/src/hooks/article/useDeleteArticle.ts index 4d563b8..07669ef 100644 --- a/src/hooks/article/useDeleteArticle.ts +++ b/src/hooks/article/useDeleteArticle.ts @@ -12,7 +12,6 @@ export const useDeleteArticle = () => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['articles'] }); - queryClient.invalidateQueries({ queryKey: ['article'] }); }, onError: (error: any) => { diff --git a/src/hooks/article/useDeleteSavaArticles.ts b/src/hooks/article/useDeleteSavaArticles.ts index ced2f0e..07fe2a2 100644 --- a/src/hooks/article/useDeleteSavaArticles.ts +++ b/src/hooks/article/useDeleteSavaArticles.ts @@ -11,6 +11,8 @@ export const useDeleteSaveArticle = () => { onSuccess: (data) => { successNotification(data?.message) + queryClient.invalidateQueries({ queryKey: ['article'] }); + queryClient.invalidateQueries({ queryKey: ['articles'] }); queryClient.invalidateQueries({ queryKey: ['saved-articles'] }); diff --git a/src/hooks/auth/useSignOut.ts b/src/hooks/auth/useSignOut.ts index 5c5b388..99a23f5 100644 --- a/src/hooks/auth/useSignOut.ts +++ b/src/hooks/auth/useSignOut.ts @@ -10,9 +10,7 @@ export const useSignOut = () => { mutationFn: signoutUser, onSuccess: (data) => { successNotification(data.message); - queryClient.invalidateQueries({ - queryKey: ['user'] - }) + queryClient.invalidateQueries({ queryKey: ['user'] }) localStorage.removeItem('ucType_'); diff --git a/src/hooks/thread/useCreateThreadComment.ts b/src/hooks/thread/useCreateThreadComment.ts index a6db9fc..495e195 100644 --- a/src/hooks/thread/useCreateThreadComment.ts +++ b/src/hooks/thread/useCreateThreadComment.ts @@ -12,7 +12,6 @@ export const useCreateThreadComment = () => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ queryKey: ['thread'] }) }, onError: (error: any) => { diff --git a/src/hooks/thread/useCreateThreadDisLike.ts b/src/hooks/thread/useCreateThreadDisLike.ts index 21b62c1..9f9ae46 100644 --- a/src/hooks/thread/useCreateThreadDisLike.ts +++ b/src/hooks/thread/useCreateThreadDisLike.ts @@ -10,9 +10,7 @@ export const useCreateThreadDisLike = () => { mutationFn: createThreadDisLike, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ queryKey: ['thread'] }); - queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { diff --git a/src/hooks/thread/useCreateThreadLike.ts b/src/hooks/thread/useCreateThreadLike.ts index 48442d3..2c3fc01 100644 --- a/src/hooks/thread/useCreateThreadLike.ts +++ b/src/hooks/thread/useCreateThreadLike.ts @@ -10,9 +10,7 @@ export const useCreateThreadLike = () => { mutationFn: createThreadLike, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['threads'] }); - queryClient.invalidateQueries({ queryKey: ['thread'] }); - queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { diff --git a/src/hooks/thread/useDeleteThread.ts b/src/hooks/thread/useDeleteThread.ts index f085735..afa4bd9 100644 --- a/src/hooks/thread/useDeleteThread.ts +++ b/src/hooks/thread/useDeleteThread.ts @@ -12,9 +12,7 @@ export const useDeleteThread = () => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['threads'] }) - queryClient.invalidateQueries({ queryKey: ['thread'] }) - queryClient.invalidateQueries({ queryKey: ['public-author-threads'] }); }, onError: (error: any) => { diff --git a/src/hooks/user/useCreateFollowUser.ts b/src/hooks/user/useCreateFollowUser.ts index fa3b068..e87389f 100644 --- a/src/hooks/user/useCreateFollowUser.ts +++ b/src/hooks/user/useCreateFollowUser.ts @@ -11,17 +11,12 @@ export const useCreateFollowUser = () => { onSuccess: (data) => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['follow-users'] }); - queryClient.invalidateQueries({ queryKey: ['limit-follow-users'] }); - queryClient.invalidateQueries({ queryKey: ['user'] }); - + queryClient.invalidateQueries({ queryKey: ['article'] }); 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) => { diff --git a/src/hooks/user/useCreateUnFollowUser.ts b/src/hooks/user/useCreateUnFollowUser.ts index 2e1922b..c90aa40 100644 --- a/src/hooks/user/useCreateUnFollowUser.ts +++ b/src/hooks/user/useCreateUnFollowUser.ts @@ -11,17 +11,12 @@ export const useCreateOnFollowUser = () => { onSuccess: (data) => { successNotification(data.message); queryClient.invalidateQueries({ queryKey: ['follow-users'] }); - queryClient.invalidateQueries({ queryKey: ['limit-follow-users'] }); - queryClient.invalidateQueries({ queryKey: ['user'] }); - + queryClient.invalidateQueries({ queryKey: ['article'] }); 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) => { diff --git a/src/hooks/user/useDeleteAccount.ts b/src/hooks/user/useDeleteAccount.ts index 03c8942..47f2c16 100644 --- a/src/hooks/user/useDeleteAccount.ts +++ b/src/hooks/user/useDeleteAccount.ts @@ -9,9 +9,7 @@ export const useDeleteAccount = () => { const deleteAccountMutation = useMutation({ mutationFn: deleteAccount, onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['user'] - }) + queryClient.invalidateQueries({ queryKey: ['user'] }) localStorage.removeItem('ucType_'); diff --git a/src/hooks/user/useUpdateAvatar.ts b/src/hooks/user/useUpdateAvatar.ts index 1de6bf9..0c5c67d 100644 --- a/src/hooks/user/useUpdateAvatar.ts +++ b/src/hooks/user/useUpdateAvatar.ts @@ -9,9 +9,7 @@ export const useUpdateAvatar = () => { const updateProfileAvatarMutation = useMutation({ mutationFn: uploadProfileAvatar, onSuccess: (data) => { - queryClient.invalidateQueries({ - queryKey: ['user'] - }); + queryClient.invalidateQueries({ queryKey: ['user'] }); successNotification(data?.message) }, diff --git a/src/hooks/user/useUpdateProfile.ts b/src/hooks/user/useUpdateProfile.ts index b44892f..c29d9f9 100644 --- a/src/hooks/user/useUpdateProfile.ts +++ b/src/hooks/user/useUpdateProfile.ts @@ -9,9 +9,7 @@ export const useUpdateProfile = () => { const updateProfileMutation = useMutation({ mutationFn: updateProfile, onSuccess: (data) => { - queryClient.invalidateQueries({ - queryKey: ['user'] - }); + queryClient.invalidateQueries({ queryKey: ['user'] }); successNotification(data?.message); }, diff --git a/src/pages/Articles/HeroSection/index.tsx b/src/pages/Articles/HeroSection/index.tsx index 792973b..0fb81df 100644 --- a/src/pages/Articles/HeroSection/index.tsx +++ b/src/pages/Articles/HeroSection/index.tsx @@ -1,67 +1,63 @@ import { FunctionComponent } from 'react' import { Link } from 'react-router-dom' import { Avatar, Box, Flex, Heading, Text } from '@chakra-ui/react' -import { FaArrowLeft } from 'react-icons/fa6' import { IArticleHeroProps } from 'src/types' +import { useUser } from '@context/userContext' const ArticleHeroSection: FunctionComponent = ({ title, authorAvatar, authorName, authorUsername, + is_following, + followUser, read_time, date }) => { + const { user } = useUser(); + return ( - - - - back to all articles - - - - {title} - - - - - - + {title} + + - {authorName} + + - {read_time} + + + + {authorName} + - + {user && ( + + {is_following ? "unfollow" : "follow"} + + )} + - {date} - - + + {read_time} • {date} + + + ) } diff --git a/src/pages/Articles/components/ArticleActionButtons.tsx b/src/pages/Articles/components/ArticleActionButtons.tsx index adaa99b..98993c9 100644 --- a/src/pages/Articles/components/ArticleActionButtons.tsx +++ b/src/pages/Articles/components/ArticleActionButtons.tsx @@ -1,97 +1,121 @@ import { FunctionComponent } from 'react' -import { Stack, Text, useDisclosure } from '@chakra-ui/react' +import { Link } from 'react-router-dom' +import { Box, Flex, HStack, Menu, MenuButton, MenuItem, MenuList, Text, Tooltip, useDisclosure } from '@chakra-ui/react' import { MdOutlineQuickreply, MdOutlineThumbDown, MdOutlineThumbUp } from 'react-icons/md' +import { BsSave } from 'react-icons/bs' +import { IoPlayCircleOutline, IoSaveSharp } from 'react-icons/io5' +import { RiShareForwardLine } from 'react-icons/ri' +import { FaFacebook, FaXTwitter, FaLinkedin } from 'react-icons/fa6' +import { getFacebookUrl, getTwitterUrl, getLinkedinUrl } from '@phntms/react-share' import { useUser } from '@context/userContext' -import { ShowLoginModal } from '@components/index' +import { ShowLoginModal, TextSpeech } from '@components/index' import { ArticleActionButtonsProps } from 'src/types' const ArticleActionButtons: FunctionComponent = ({ article, onLike, onDisLike, - onShowComment + onShowComment, + isLoggedIn, + is_saved, + isOwner, + saveUnsavedArticle }) => { const { user } = useUser(); - const { onClose, onOpen, isOpen} = useDisclosure(); - - return ( - <> - - - - {article?.article_comment_counts} + const { onClose, onOpen, isOpen } = useDisclosure(); + const url = window.location.href; + + const renderSaveButton = () => ( + !isOwner && isLoggedIn && ( + + + {is_saved ? ( + + ) : ( + + )} - - {user ? ( - <> - - {!article?.user_liked_article && ( - - )} + + ) + ); + + const renderShareMenu = () => ( + + + + + + + Share on Facebook + + + Share on X + + + Share on LinkedIn + + + + ); + + const renderActionButtons = () => ( + + {renderSaveButton()} + + + {renderShareMenu()} + + + ); + + const renderLikesSection = () => ( + + {!article?.user_liked_article ? ( + + ) : ( + + )} + {article?.article_like_counts} + + ); - {article?.user_liked_article && ( - - )} - {article?.article_like_counts} - - - ) : ( - <> - - - {article?.article_like_counts} - - + const renderCommentSection = () => ( + + + {article?.article_comment_counts} + + ); + + return ( + + {user ? renderActionButtons() : ( + + + + + + + + + + + + )} + + + {renderCommentSection()} + {user ? renderLikesSection() : ( + + + {article?.article_like_counts} + )} - - - - - ) -} + + + + + ); +}; export default ArticleActionButtons; \ No newline at end of file diff --git a/src/pages/Articles/components/ArticleForm.tsx b/src/pages/Articles/components/ArticleForm.tsx index f691ef0..f8ad577 100644 --- a/src/pages/Articles/components/ArticleForm.tsx +++ b/src/pages/Articles/components/ArticleForm.tsx @@ -1,11 +1,5 @@ import { FormEvent, FunctionComponent, useEffect, useRef, useState } from 'react' -import { - Box, - FormControl, - Image, - Input, - Text -} from '@chakra-ui/react' +import { Box, FormControl, Image, Input, Text } from '@chakra-ui/react' import { FaUpload, FaImage } from 'react-icons/fa6' import { Helmet } from 'react-helmet-async' @@ -32,17 +26,11 @@ const ArticleForm: FunctionComponent = ({ const { editArticleMutation } = useEditArticle(); const handleUploadClick = () => { - if (fileInputRef.current) { - fileInputRef.current.click(); - } + if (fileInputRef.current) fileInputRef.current.click(); }; const { handleFileUpload, loading: imageUploadLoading } = useImageUpload({ - onSuccess: (data) => { - setThumbnail(data?.data?.imageUploadUrl) - console.log(data?.data?.imageUploadUrl); - - }, + onSuccess: (data) => { setThumbnail(data?.data?.imageUploadUrl)} }); const handleSubmitArticle = (event: FormEvent) => { @@ -178,4 +166,4 @@ const ArticleForm: FunctionComponent = ({ ) } -export default ArticleForm \ No newline at end of file +export default ArticleForm; \ No newline at end of file diff --git a/src/pages/Articles/index.tsx b/src/pages/Articles/index.tsx index 700f497..24a91ad 100644 --- a/src/pages/Articles/index.tsx +++ b/src/pages/Articles/index.tsx @@ -3,15 +3,7 @@ import { Link } from 'react-router-dom' import { Box, Grid, GridItem, Heading, useDisclosure } from '@chakra-ui/react' import { Helmet } from 'react-helmet-async' -import { - LatestArticleCard, - FollowCard, - // RecommendTopicCard, - ArticlesCard, - Button, - Alert, - Skeleton -} from '@components/index' +import { LatestArticleCard, FollowCard, ArticlesCard, Button, Alert, Skeleton } from '@components/index' import { useUser } from '@context/userContext' import { useGetPaginatedArticles } from '@hooks/article/useGetPaginatedArticles' import { useDeleteArticle } from '@hooks/article/useDeleteArticle' @@ -23,14 +15,7 @@ import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' const Articles: FunctionComponent = () => { const { user } = useUser(); - const { - articles, - isLoading, - isSuccess, - hasNextPage, - fetchNextPage, - isFetchingNextPage - } = useGetPaginatedArticles(25) + const { articles, isLoading, isSuccess, hasNextPage, fetchNextPage, isFetchingNextPage } = useGetPaginatedArticles(25) const { deleteArticleMutation } = useDeleteArticle() const { data: pinArticles } = useGetPinnedArticles(); const { createSaveArticleMutation } = useCreateSaveArticle(); @@ -52,20 +37,12 @@ const Articles: FunctionComponent = () => { } const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { - if (is_saved) { - deleteSaveArticleMutation.mutate(articleId); - } else { - createSaveArticleMutation.mutate(articleId); - } - } + is_saved ? deleteSaveArticleMutation.mutate(articleId) : createSaveArticleMutation.mutate(articleId); + }; - const handleFollowUnfollow = (userId: string, following: boolean) => { - if (following) { - createOnFollowUserMutation.mutate(userId) - } else { - createFollowUserMutation.mutate(userId) - } - } + const handleFollowUnfollow = (userId: string, following: boolean) => { + following ? createOnFollowUserMutation.mutate(userId) : createFollowUserMutation.mutate(userId); + }; return ( <> diff --git a/src/pages/Articles/show/index.tsx b/src/pages/Articles/show/index.tsx index 3eff8c7..af61937 100644 --- a/src/pages/Articles/show/index.tsx +++ b/src/pages/Articles/show/index.tsx @@ -3,20 +3,24 @@ import { Link, useParams } from 'react-router-dom' import { Avatar, Box, Flex, HStack, Image, Text } from '@chakra-ui/react' import { FaGithub, FaXTwitter } from 'react-icons/fa6' import { GiWorld } from 'react-icons/gi' +import { Helmet } from 'react-helmet-async' import ArticleHeroSection from '@pages/Articles/HeroSection' -import { useGetSingleArticle } from '@hooks/article/useGetSingleArticle' -import truncate from '@helpers/truncate' -import { NotFound, Skeleton } from '@components/index' import CommentDrawer from '@pages/Articles/components/CommentDrawer' +import ArticleActionButtons from '@pages/Articles/components/ArticleActionButtons' import { useArticleActions } from '@pages/Articles/hooks/useArticleActions' -import ArticleActionButtons from '../components/ArticleActionButtons' -import ContentBlockContent from '@components/CodeBlockContent' -import { Helmet } from 'react-helmet-async' + +import { ContentBlockContent, NotFound, Skeleton } from '@components/index' +import truncate from '@helpers/truncate' + +import { useGetSingleArticle } from '@hooks/article/useGetSingleArticle' +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 ShowArticle: FunctionComponent = () => { const { id } = useParams(); - const { data, isLoading, isSuccess, error } = useGetSingleArticle(id!); const { comment, @@ -27,8 +31,70 @@ const ShowArticle: FunctionComponent = () => { handleCommentSubmit, handleLikeArticle, handleDisLikeArticle, - isSubmittingArticleComment - } = useArticleActions(id!) + isSubmittingArticleComment, + } = useArticleActions(id!); + const { createSaveArticleMutation } = useCreateSaveArticle(); + const { deleteSaveArticleMutation } = useDeleteSaveArticle(); + const { createFollowUserMutation } = useCreateFollowUser(); + const { createOnFollowUserMutation } = useCreateOnFollowUser(); + + const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { + is_saved ? deleteSaveArticleMutation.mutate(articleId) : createSaveArticleMutation.mutate(articleId); + }; + + const handleFollowUnfollow = (userId: string, following: boolean) => { + following ? createOnFollowUserMutation.mutate(userId) : createFollowUserMutation.mutate(userId); + }; + + const renderAuthorSocialLinks = () => ( + + {data?.data?.author?.info_details?.gitHub && ( + + + + )} + {data?.data?.author?.info_details?.twitter && ( + + + + )} + {data?.data?.author?.info_details?.website && ( + + + + )} + + ); + + const renderAuthorInfo = () => ( + + + + + + + + + {data?.data?.author?.fullname} + + + + {truncate(data?.data?.author?.info_details?.bio, 230)} + + + + + {renderAuthorSocialLinks()} + + + ); if (error) return ; @@ -42,141 +108,37 @@ const ShowArticle: FunctionComponent = () => { {`${data?.data?.title} | learn-hub`} - <> - handleFollowUnfollow(data?.data?.author?.id, data?.data?.author?.is_following)} + /> + + + handleShowComment(data?.data?.id)} + isLoggedIn + isOwner={data?.data?.isOwner} + is_saved={data?.data?.is_saved} + saveUnsavedArticle={() => handleSaveUnsavedArticle(data?.data?.id, data?.data?.is_saved)} /> - - - handleShowComment(data?.data?.id)} - /> - - - - - - - - - - - - - - - - - - - - - {data?.author?.fullname} - - - - {truncate(data?.data?.author?.info_details?.bio, 230)} - - - - - - - {data?.data?.author?.info_details?.gitHub && ( - - - - - - )} - - {data?.data?.author?.info_details?.twitter && ( - - - - - - )} - - {data?.data?.author?.info_details?.website && ( - - - - - - )} - - - - - + + + + + + {renderAuthorInfo()} - + { )} - ) -} + ); +}; export default ShowArticle; \ 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 e1a272e..af1a724 100644 --- a/src/pages/Home/Authenticated/Following/index.tsx +++ b/src/pages/Home/Authenticated/Following/index.tsx @@ -2,8 +2,8 @@ import { Fragment, FunctionComponent } from 'react' import { Box, Heading } from '@chakra-ui/react' import { ArticlesCard, Button, Skeleton } from '@components/index' -import { useGetFollowUsersArticles } from '@hooks/user/useGetFollowUserArticles' import { useUser } from '@context/userContext' +import { useGetFollowUsersArticles } from '@hooks/user/useGetFollowUserArticles' import { useCreateSaveArticle } from '@hooks/article/useCreateSaveArticles' import { useDeleteSaveArticle } from '@hooks/article/useDeleteSavaArticles' import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' @@ -15,35 +15,19 @@ interface FollowingProps { const Following: FunctionComponent = ({ setTabIndex }) => { const { user } = useUser(); - const { - articles, - isSuccess, - isLoading, - hasNextPage, - isFetchingNextPage, - fetchNextPage - } = useGetFollowUsersArticles(25); + 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); - } - } + is_saved ? deleteSaveArticleMutation.mutate(articleId) : createSaveArticleMutation.mutate(articleId); + }; const handleFollowUnfollow = (userId: string, following: boolean) => { - if (following) { - createOnFollowUserMutation.mutate(userId) - } else { - createFollowUserMutation.mutate(userId) - } - } + following ? createOnFollowUserMutation.mutate(userId) : createFollowUserMutation.mutate(userId); + }; return ( <> diff --git a/src/pages/Home/Authenticated/ForYou/index.tsx b/src/pages/Home/Authenticated/ForYou/index.tsx index 5ffc156..8385730 100644 --- a/src/pages/Home/Authenticated/ForYou/index.tsx +++ b/src/pages/Home/Authenticated/ForYou/index.tsx @@ -12,17 +12,10 @@ import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' const ForYou = () => { const { user } = useUser(); - const { - articles, - isLoading, - isSuccess, - hasNextPage, - fetchNextPage, - isFetchingNextPage - } = useGetRecommentedArticles(20) + const { articles, isLoading, isSuccess, hasNextPage, fetchNextPage, isFetchingNextPage } = 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(); @@ -40,20 +33,12 @@ const ForYou = () => { } const handleSaveUnsavedArticle = (articleId: string, is_saved: boolean) => { - if (is_saved) { - deleteSaveArticleMutation.mutate(articleId); - } else { - createSaveArticleMutation.mutate(articleId); - } - } + is_saved ? deleteSaveArticleMutation.mutate(articleId) : createSaveArticleMutation.mutate(articleId); + }; - const handleFollowUnfollow = (userId: string, following: boolean) => { - if (following) { - createOnFollowUserMutation.mutate(userId) - } else { - createFollowUserMutation.mutate(userId) - } - } + const handleFollowUnfollow = (userId: string, following: boolean) => { + following ? createOnFollowUserMutation.mutate(userId) : createFollowUserMutation.mutate(userId); + }; return ( <> diff --git a/src/pages/Home/Authenticated/index.tsx b/src/pages/Home/Authenticated/index.tsx index b2721cc..399d90e 100644 --- a/src/pages/Home/Authenticated/index.tsx +++ b/src/pages/Home/Authenticated/index.tsx @@ -1,14 +1,5 @@ import { FunctionComponent, useState } from 'react' -import { - Box, - Container, - Tab, - TabIndicator, - TabList, - TabPanel, - TabPanels, - Tabs -} from '@chakra-ui/react' +import { Box, Container, Tab, TabIndicator, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' import { colors } from '../../../colors' import { FollowCard, RecommendTopicCard } from '@components/index' diff --git a/src/pages/Home/Public/Articles/index.tsx b/src/pages/Home/Public/Articles/index.tsx index fbf4757..eedb3a8 100644 --- a/src/pages/Home/Public/Articles/index.tsx +++ b/src/pages/Home/Public/Articles/index.tsx @@ -8,7 +8,7 @@ import { colors } from '../../../../colors' import { useGetArticles } from '@hooks/article/useGetArticles' const HomeArticles: FunctionComponent = () => { - const { data: articles, isLoading, isSuccess } = useGetArticles(9) + const { data: articles, isLoading, isSuccess } = useGetArticles(6) return ( diff --git a/src/pages/Home/Public/Threads/index.tsx b/src/pages/Home/Public/Threads/index.tsx index 8b4d6d3..f2dd250 100644 --- a/src/pages/Home/Public/Threads/index.tsx +++ b/src/pages/Home/Public/Threads/index.tsx @@ -18,6 +18,8 @@ const HomeThreads: FunctionComponent = () => { > Threads + {isLoading && } + {threads && isSuccess && ( <> { )} - - {isLoading && } - ) } diff --git a/src/pages/Search/articles.tsx b/src/pages/Search/articles.tsx index cfd7315..5cbf9fb 100644 --- a/src/pages/Search/articles.tsx +++ b/src/pages/Search/articles.tsx @@ -6,7 +6,7 @@ import truncate from '@helpers/truncate' import LoadButton from '@pages/Search/components/loadButton' import { ISearchArticles } from 'src/types' -const SearchArticles: FunctionComponent = ({ +const SearchArticles: FunctionComponent = ({ articles, hasMore, fetchNext, @@ -23,20 +23,19 @@ const SearchArticles: FunctionComponent = ({ } spacing='4'> {articles?.map((article: any, index: number) => ( - - + + {truncate(article?.title, 200)} - - - + + ))} diff --git a/src/pages/Search/threads.tsx b/src/pages/Search/threads.tsx index a126019..8679b4e 100644 --- a/src/pages/Search/threads.tsx +++ b/src/pages/Search/threads.tsx @@ -6,11 +6,11 @@ import truncate from '@helpers/truncate' import LoadButton from '@pages/Search/components/loadButton' import { ISearchThreads } from 'src/types' -const SearchThreads: FunctionComponent = ({ +const SearchThreads: FunctionComponent = ({ threads, hasMore, fetchNext, - isFetching + isFetching }) => { return ( @@ -23,19 +23,19 @@ const SearchThreads: FunctionComponent = ({ } spacing='4'> {threads?.map((thread: any, index: number) => ( - - + + {truncate(thread?.title, 200)} - - + + ))} diff --git a/src/pages/Search/users.tsx b/src/pages/Search/users.tsx index 6e89d14..ee72f3d 100644 --- a/src/pages/Search/users.tsx +++ b/src/pages/Search/users.tsx @@ -6,7 +6,7 @@ import truncate from '@helpers/truncate' import LoadButton from '@pages/Search/components/loadButton' import { ISearchUsers } from 'src/types' -const SearchUsers: FunctionComponent = ({ +const SearchUsers: FunctionComponent = ({ users, hasMore, fetchNext, @@ -23,16 +23,16 @@ const SearchUsers: FunctionComponent = ({ } spacing='4'> {users?.map((user: any, index: number) => ( - - + + @@ -43,8 +43,8 @@ const SearchUsers: FunctionComponent = ({ {truncate(user?.bio, 190)} - - + + ))} diff --git a/src/pages/Threads/index.tsx b/src/pages/Threads/index.tsx index 9089a27..6d003dd 100644 --- a/src/pages/Threads/index.tsx +++ b/src/pages/Threads/index.tsx @@ -1,15 +1,6 @@ import { Fragment, FunctionComponent } from 'react' import { Link } from 'react-router-dom' -import { - Box, - Card, - CardBody, - CardHeader, - Heading, - Stack, - StackDivider, - Text -} from '@chakra-ui/react' +import { Box, Card, CardBody, CardHeader, Heading, Stack, StackDivider, Text } from '@chakra-ui/react' import { Helmet } from 'react-helmet-async' import { colors } from '../../colors' diff --git a/src/pages/Users/AuthoredViews/Articles/index.tsx b/src/pages/Users/AuthoredViews/Articles/index.tsx index 6526543..c554a30 100644 --- a/src/pages/Users/AuthoredViews/Articles/index.tsx +++ b/src/pages/Users/AuthoredViews/Articles/index.tsx @@ -62,11 +62,14 @@ const ArthoredArticles: FunctionComponent = () => {