From 1db80c9ea854b956c4830dc9200d40f5758c4d91 Mon Sep 17 00:00:00 2001 From: ITurres Date: Fri, 8 Mar 2024 21:44:19 -0300 Subject: [PATCH 1/5] Refactor: 'getLikes' async function in 'involvementAPI.ts' - Removed unnecessary try-catch block in the `getLikes` function. Instead, if the response is not ok, the function now throws an error class with the response `statusText` as the message. This refactor improves the clarity and efficiency of error handling. --- src/services/involvementAPI/involvementAPI.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/services/involvementAPI/involvementAPI.ts b/src/services/involvementAPI/involvementAPI.ts index 0d70db0..78be27b 100644 --- a/src/services/involvementAPI/involvementAPI.ts +++ b/src/services/involvementAPI/involvementAPI.ts @@ -22,25 +22,23 @@ const postLike = async (itemId: string) => { }; const getLikes = async () => { - try { - const response = await fetch(`${baseURL}${appId}/likes/`, { - method: 'GET', - }); - - if (!response.ok) return []; + const response = await fetch(`${baseURL}${appId}/likes/`, { + method: 'GET', + }); - // * Only parse the response if it's JSON. - // * specially when the involvementAPI : 'appId' is new, the response to be parsed wont be JSON. - const contentType = response.headers.get('Content-Type'); - if (contentType && contentType.includes('application/json')) { - const likes = await response.json(); - if (likes instanceof Array) return likes; - } + if (!response.ok) { + throw new Error(`Error fetching likes: ${response.statusText}`); + } - return []; - } catch (error) { - return error; + // * Only parse the response if it's JSON. + // * specially when the involvementAPI : 'appId' is new, the response to be parsed wont be JSON. + const contentType = response.headers.get('Content-Type'); + if (contentType && contentType.includes('application/json')) { + const likes = await response.json(); + if (likes instanceof Array) return likes; } + + return []; }; const involvement = { From 98e8cff69bf539feff71ddc1a49057903a121c3f Mon Sep 17 00:00:00 2001 From: ITurres Date: Fri, 8 Mar 2024 21:47:25 -0300 Subject: [PATCH 2/5] Feat: Integrate projects likes fetching and error handling in 'ProjectsPage' - Imported 'involvement' from 'services/involvementAPI/involvementAPI.ts'. - Set a new local state for the projects likes that will be fetched from the API. - Created the asynchronous function 'getProjectsLikes' that fetches all the projects likes within a try-catch block. If successful, the likes are set in the local state. If an error occurs, it's logged, and the local state is set to an object with 'error' and 'message' properties for error handling in the UI component 'LikeButton'. - Passed down the new local state 'projectsLikes' to 'SplideCarousel' => 'SplideCarouselSlide' to reach the 'LikeButton' component for error handling. --- src/components/pages/ProjectsPage.tsx | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/pages/ProjectsPage.tsx b/src/components/pages/ProjectsPage.tsx index 7a1dace..0be8269 100644 --- a/src/components/pages/ProjectsPage.tsx +++ b/src/components/pages/ProjectsPage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import '../../styles/pages/ProjectsPage.scss'; @@ -8,14 +8,43 @@ import SplideCarousel from '../UI/SplideCarousel.tsx'; import getRandomId from '../../utils/getRandomId.ts'; import setPageTitle from '../../utils/setPageTitle.ts'; +import involvement from '../../services/involvementAPI/involvementAPI.ts'; + const ProjectsPage: React.FC = () => { setPageTitle('My projects 😊'); + const [projectsLikes, setProjectsLikes] = useState([{}]); + + const getProjectsLikes = useCallback(async () => { + try { + const requestedProjectsLikes = (await involvement.getLikes()) as Array<{}>; + + setProjectsLikes(requestedProjectsLikes); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + setProjectsLikes([ + { + error: true, + message: error, + }, + ]); + } + }, []); + + useEffect(() => { + getProjectsLikes(); + }, [getProjectsLikes]); + return (
{Object.values(projects).map((projectGroup) => ( - + ))}
From 78b32c62d53d5f992eb7a9ba3e80ad84e66dfb93 Mon Sep 17 00:00:00 2001 From: ITurres Date: Fri, 8 Mar 2024 21:52:31 -0300 Subject: [PATCH 3/5] Feat: Pass down 'projectsLikes' prop through 'SplideCarousel' - Added a new property to the `SplideCarouselProps` interface, `projectsLikes`, that will be passed down to the `SplideCarouselSlide` component. --- src/components/UI/SplideCarousel.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/UI/SplideCarousel.tsx b/src/components/UI/SplideCarousel.tsx index 4500e2e..a99e0f1 100644 --- a/src/components/UI/SplideCarousel.tsx +++ b/src/components/UI/SplideCarousel.tsx @@ -10,9 +10,13 @@ import getRandomId from '../../utils/getRandomId.ts'; /* eslint-disable no-undef */ interface SplideCarouselProps { projectsGroup: Array<{}>; + projectsLikes: Array<{}>; } -const SplideCarousel: React.FC = ({ projectsGroup }) => ( +const SplideCarousel: React.FC = ({ + projectsGroup, + projectsLikes, +}) => ( <> = ({ projectsGroup }) => ( }} > {projectsGroup.map((projectData) => ( - + ))} From b1b9ea927f74a5404d87c3bd4c2b065af5facd57 Mon Sep 17 00:00:00 2001 From: ITurres Date: Fri, 8 Mar 2024 21:54:09 -0300 Subject: [PATCH 4/5] Feat: Pass down 'projectsLikes' prop through 'SplideCarouselSlide' - Added a new property to the `SplideCarouselSlideProps` interface, `projectsLikes`, that will be passed down to the `LikeButton` component. --- src/components/UI/SplideCarouselSlide.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/UI/SplideCarouselSlide.tsx b/src/components/UI/SplideCarouselSlide.tsx index 5f4297a..2f37469 100644 --- a/src/components/UI/SplideCarouselSlide.tsx +++ b/src/components/UI/SplideCarouselSlide.tsx @@ -24,10 +24,12 @@ interface SplideCarouselSlideProps { sourceCodeHref: string; }; }; + projectsLikes: Array<{}>; } const SplideCarouselSlide: React.FC = ({ projectData, + projectsLikes, }) => (
@@ -53,7 +55,7 @@ const SplideCarouselSlide: React.FC = ({ Description

{projectData.data.description.join(' ')}

- +
{projectData.data.stack.map((stackItem) => ( From cffadb4fe4469375f9a66aec4b3f4da425ad9034 Mon Sep 17 00:00:00 2001 From: ITurres Date: Fri, 8 Mar 2024 22:17:12 -0300 Subject: [PATCH 5/5] Feat: Update 'LikeButton' component logic - Moved the Like interface outside the component and added a new `error` property. - Updated 'LikeButtonProps' interface to include `projectsLikes` prop. - Renamed 'setLoadingToFalse' function to 'setLoadingFalseAfterTimeout'. - Removed projects likes fetching logic from 'updateLikeCount'. - Added new conditions in 'updateLikeCount' to handle various scenarios. - Updated 'handleLikeSubmit' function to handle error states and removed unnecessary function invocations. --- src/components/UI/LikeButton.tsx | 66 +++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/src/components/UI/LikeButton.tsx b/src/components/UI/LikeButton.tsx index 701368a..3de61be 100644 --- a/src/components/UI/LikeButton.tsx +++ b/src/components/UI/LikeButton.tsx @@ -13,11 +13,19 @@ import '../../styles/animations/loading-icon.scss'; import involvement from '../../services/involvementAPI/involvementAPI.ts'; +interface Like { + // * disable camelcase since 'item_id' is the name of the property in the API response. + // eslint-disable-next-line camelcase + item_id: string; + likes: number; + error?: boolean; +} interface LikeButtonProps { itemId: string; + projectsLikes: Like[]; } -const LikeButton: React.FC = ({ itemId }) => { +const LikeButton: React.FC = ({ itemId, projectsLikes }) => { const [wasLiked, setWasLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); const [error, setError] = useState(false); @@ -41,61 +49,77 @@ const LikeButton: React.FC = ({ itemId }) => { // ! terminate the loading state after a certain time. // ? this is to prevent the loading state from being stuck. // * and show the user the like button again. - const setLoadingToFalse = useCallback(() => { + const setLoadingFalseAfterTimeout = useCallback(() => { setTimeout(() => { setLoading(false); }, maximumLoadingTime); }, []); - interface Like { - // * disable camelcase since 'item_id' is the name of the property in the API response. - // eslint-disable-next-line camelcase - item_id: string; - likes: number; - } - - const updateLikeCount = useCallback(async () => { - const likes: Like[] = (await involvement.getLikes()) as Like[]; + const updateLikeCount = useCallback(() => { + // ? When the Involvement API App ID is new, it will return an empty ary with an empty object + // ? while fetching the projectsLikes at 'projectsPage', + // * here we will receive an array with one empty object, thats the 1 length. + // * so just terminate the loading state, and show the like button with no likes. + if (projectsLikes.length === 1) { + setLoadingFalseAfterTimeout(); + return; + } - if (likes.length === 0) { + // ? If an error was catch at the 'projectsPage' while fetching the projectsLikes, + // ? we will receive an array with one object with the error property set to true. + if (projectsLikes[0].error) { setError(true); // ? at this point the loading spinner is still showing. // * so terminate the loading state, to show the error message. - setLoadingToFalse(); + setLoadingFalseAfterTimeout(); return; } - const likedProject = likes.find((like) => like.item_id === itemId); + const likedProject = projectsLikes.find( + (project) => project.item_id === itemId, + ) as Like | undefined; if (likedProject) { setLikeCount(likedProject.likes); // ? at this point the loading spinner is still showing because of the initial API request. // * so terminate the loading state, to show the like button with the like count. - setLoadingToFalse(); + setLoadingFalseAfterTimeout(); + } else { + // * If a project has no likes, then terminate the loading state, + // * to show the like button with no likes. + setLoadingFalseAfterTimeout(); } - }, [itemId, setLoadingToFalse]); + }, [itemId, setLoadingFalseAfterTimeout, projectsLikes]); useEffect(() => { updateLikeCount(); }, [itemId, updateLikeCount]); const handleLikeSubmit = async () => { - setWasLiked((liked) => !liked); + // ? Since 'wasLiked' was false, now it will be set to true. This is to + // ? trigger the function 'styleButton' to add the 'liked' class to the button. + setWasLiked((wasLiked) => !wasLiked); // * shows an instant update of the like count. if (!wasLiked) { setLikeCount((count) => count + 1); + // * set 'wasLiked' to false, so that the user can like the project again. + setWasLiked((wasLiked) => !wasLiked); } - if (error) { + if (projectsLikes[0].error || error) { // ? at this point, there was an error already, so user will attempt to like again. // * so trigger the loading state again. setLoading(true); // * after some time, terminate the loading state, to show like button again. - setLoadingToFalse(); + setLoadingFalseAfterTimeout(); + // * and finally show the error message. + setError(true); return; } + // ? If after all, the error state is still false, then the user can like the project. + // * i.e. POST the like to the API. const likePosted = await involvement.postLike(itemId); if (likePosted === 'error') { @@ -104,8 +128,6 @@ const LikeButton: React.FC = ({ itemId }) => { } setLoading(false); - // * after posting a like, update the like count. - updateLikeCount(); }; if (loading) { @@ -137,7 +159,7 @@ const LikeButton: React.FC = ({ itemId }) => { {(likeCount > 0 || error) && (   - {!error ? likeCount : 'Error'} + {error ? 'Error' : likeCount } )}