From 5eaf6be2de998ec323aedc0c92d278d1bedff22a Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 16 May 2024 17:55:20 +0100 Subject: [PATCH 1/3] fix multiple available hearts data structure --- packages/berlin/src/pages/Comments.tsx | 35 +++++++++++++++++---- packages/berlin/src/pages/Cycle.tsx | 43 +++++++++++++++++++------- packages/berlin/src/store/index.ts | 13 +++++--- packages/berlin/src/utils/constants.ts | 2 ++ 4 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 packages/berlin/src/utils/constants.ts diff --git a/packages/berlin/src/pages/Comments.tsx b/packages/berlin/src/pages/Comments.tsx index b0c376bb..c3323ccb 100644 --- a/packages/berlin/src/pages/Comments.tsx +++ b/packages/berlin/src/pages/Comments.tsx @@ -44,6 +44,7 @@ import CommentsTable from '../components/tables/comment-table'; import CommentsColumns from '../components/columns/comments-columns'; import IconButton from '../components/icon-button'; import Textarea from '../components/textarea'; +import { INITIAL_HEARTS } from '../utils/constants'; type LocalUserVotes = ResponseUserVotesType | { optionId: string; numOfVotes: number }[]; @@ -52,8 +53,6 @@ function Comments() { const queryClient = useQueryClient(); const { cycleId, optionId } = useParams(); const { user } = useUser(); - const availableHearts = useAppStore((state) => state.availableHearts); - const setAvailableHearts = useAppStore((state) => state.setAvailableHearts); const [localUserVotes, setLocalUserVotes] = useState([]); const [localOptionHearts, setLocalOptionHearts] = useState(0); const [comment, setComment] = useState(''); @@ -65,6 +64,10 @@ function Comments() { enabled: !!optionId, }); + const availableHearts = + useAppStore((state) => state.availableHearts[option?.questionId || '']) ?? INITIAL_HEARTS; + const setAvailableHearts = useAppStore((state) => state.setAvailableHearts); + const { data: userVotes } = useQuery({ queryKey: ['votes', cycleId], queryFn: () => fetchUserVotes(cycleId || ''), @@ -82,7 +85,6 @@ function Comments() { queryKey: ['option', optionId, 'comments'], queryFn: () => fetchComments({ optionId: optionId || '' }), enabled: !!optionId, - // refetchInterval: 5000, // Poll every 5 seconds }); const sortedComments = useMemo(() => { @@ -100,8 +102,13 @@ function Comments() { const hearts = userVotes?.find((option) => optionId === option.optionId)?.numOfVotes || 0; setLocalOptionHearts(hearts); setLocalUserVotes([{ optionId: optionId, numOfVotes: hearts }]); + // update the available hearts + setAvailableHearts({ + questionId: option?.questionId ?? '', + hearts: INITIAL_HEARTS - hearts, + }); } - }, [optionId, userVotes]); + }, [optionId, userVotes, setAvailableHearts, option?.questionId]); const { mutate: mutateComments } = useMutation({ mutationFn: postComment, @@ -113,15 +120,31 @@ function Comments() { }); const handleVoteWrapper = (optionId: string) => { + if (availableHearts === 0) { + toast.error('No hearts left to give'); + return; + } + setLocalOptionHearts((prevLocalOptionHearts) => prevLocalOptionHearts + 1); setLocalUserVotes((prevLocalUserVotes) => handleLocalVote(optionId, prevLocalUserVotes)); - setAvailableHearts(handleAvailableHearts(availableHearts, 'vote')); + setAvailableHearts({ + questionId: option?.questionId ?? '', + hearts: handleAvailableHearts(availableHearts, 'vote'), + }); }; const handleUnVoteWrapper = (optionId: string) => { + if (availableHearts === INITIAL_HEARTS) { + toast.error('No votes to left to remove'); + return; + } + setLocalOptionHearts((prevLocalOptionHearts) => Math.max(0, prevLocalOptionHearts - 1)); setLocalUserVotes((prevLocalUserVotes) => handleLocalUnVote(optionId, prevLocalUserVotes)); - setAvailableHearts(handleAvailableHearts(availableHearts, 'unVote')); + setAvailableHearts({ + questionId: option?.questionId ?? '', + hearts: handleAvailableHearts(availableHearts, 'unVote'), + }); }; const { mutate: mutateVotes } = useMutation({ diff --git a/packages/berlin/src/pages/Cycle.tsx b/packages/berlin/src/pages/Cycle.tsx index 04d93b3e..96a34a06 100644 --- a/packages/berlin/src/pages/Cycle.tsx +++ b/packages/berlin/src/pages/Cycle.tsx @@ -35,13 +35,11 @@ import BackButton from '../components/back-button'; import Button from '../components/button'; import CycleColumns from '../components/columns/cycle-columns'; import OptionCard from '../components/option-card'; +import { FIVE_MINUTES_IN_SECONDS, INITIAL_HEARTS } from '../utils/constants'; type Order = 'asc' | 'desc'; type LocalUserVotes = ResponseUserVotesType | { optionId: string; numOfVotes: number }[]; -const initialHearts = 20; -const fiveMinutesInSeconds = 300; - function Cycle() { const queryClient = useQueryClient(); const { user } = useUser(); @@ -58,7 +56,10 @@ function Cycle() { enabled: !!user?.id && !!cycleId, retry: false, }); - const { availableHearts, setAvailableHearts } = useAppStore((state) => state); + const availableHearts = + useAppStore((state) => state.availableHearts[cycle?.forumQuestions[0].id || '']) ?? + INITIAL_HEARTS; + const setAvailableHearts = useAppStore((state) => state.setAvailableHearts); const [startAt, setStartAt] = useState(null); const [endAt, setEndAt] = useState(null); const [localUserVotes, setLocalUserVotes] = useState([]); @@ -83,7 +84,7 @@ function Cycle() { case 'upcoming': return `Vote opens in: ${formattedTime}`; case 'open': - if (time && time <= fiveMinutesInSeconds) { + if (time && time <= FIVE_MINUTES_IN_SECONDS) { return `Vote closes in: ${formattedTime}`; } else if (time === 0) { return 'Vote has ended.'; @@ -94,12 +95,16 @@ function Cycle() { } }, [cycleState, time, formattedTime]); - const updateVotesAndHearts = (votes: ResponseUserVotesType) => { + const updateInitialVotesAndHearts = (votes: ResponseUserVotesType) => { const givenVotes = votes .map((option) => option.numOfVotes) .reduce((prev, curr) => prev + curr, 0); - setAvailableHearts(initialHearts - givenVotes); + setAvailableHearts({ + questionId: cycle?.forumQuestions[0].id || '', + hearts: INITIAL_HEARTS - givenVotes, + }); + setLocalUserVotes(votes); }; @@ -122,7 +127,7 @@ function Cycle() { useEffect(() => { if (userVotes?.length) { - updateVotesAndHearts(userVotes); + updateInitialVotesAndHearts(userVotes); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [userVotes]); @@ -142,13 +147,29 @@ function Cycle() { }); const handleVoteWrapper = (optionId: string) => { + if (availableHearts === 0) { + toast.error('No hearts left to give'); + return; + } + setLocalUserVotes((prevLocalUserVotes) => handleLocalVote(optionId, prevLocalUserVotes)); - setAvailableHearts(handleAvailableHearts(availableHearts, 'vote')); + setAvailableHearts({ + questionId: cycle?.forumQuestions[0].id ?? '', + hearts: handleAvailableHearts(availableHearts, 'vote'), + }); }; const handleUnVoteWrapper = (optionId: string) => { + if (availableHearts === INITIAL_HEARTS) { + toast.error('No votes to left to remove'); + return; + } + setLocalUserVotes((prevLocalUserVotes) => handleLocalUnVote(optionId, prevLocalUserVotes)); - setAvailableHearts(handleAvailableHearts(availableHearts, 'unVote')); + setAvailableHearts({ + questionId: cycle?.forumQuestions[0].id ?? '', + hearts: handleAvailableHearts(availableHearts, 'unVote'), + }); }; const handleSaveVotesWrapper = () => { @@ -224,7 +245,7 @@ function Cycle() { You have {availableHearts} hearts left to give away: - {Array.from({ length: initialHearts }).map((_, id) => ( + {Array.from({ length: INITIAL_HEARTS }).map((_, id) => ( void; setOnboardingStatus: (status: COMPLETION_STATUS) => void; - setAvailableHearts: (hearts: number) => void; + setAvailableHearts: ({ hearts, questionId }: { questionId: string; hearts: number }) => void; toggleTheme: () => void; reset: () => void; } @@ -23,17 +25,18 @@ export const useAppStore = create()( userStatus: 'INCOMPLETE', eventRegistrationStatus: 'INCOMPLETE', theme: 'dark', // Default theme is dark - availableHearts: 20, // Set the initial hearts value + availableHearts: {}, // Set the initial hearts value setUserStatus: (status: COMPLETION_STATUS) => set(() => ({ userStatus: status })), setOnboardingStatus: (status: COMPLETION_STATUS) => set(() => ({ onboardingStatus: status })), - setAvailableHearts: (hearts: number) => set(() => ({ availableHearts: hearts })), + setAvailableHearts: ({ hearts, questionId }) => + set((state) => ({ availableHearts: { ...state.availableHearts, [questionId]: hearts } })), toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })), reset: () => set(() => ({ userStatus: 'INCOMPLETE', eventRegistrationStatus: 'INCOMPLETE', - availableHearts: 20, + availableHearts: {}, })), }), { name: 'lexicon-store' }, diff --git a/packages/berlin/src/utils/constants.ts b/packages/berlin/src/utils/constants.ts new file mode 100644 index 00000000..6a3d2cba --- /dev/null +++ b/packages/berlin/src/utils/constants.ts @@ -0,0 +1,2 @@ +export const INITIAL_HEARTS = 20; +export const FIVE_MINUTES_IN_SECONDS = 300; From d19709405e104139129ef43651601efb0bc571bb Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 16 May 2024 18:12:04 +0100 Subject: [PATCH 2/3] fix available hearts in comments page --- packages/berlin/src/pages/Comments.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/berlin/src/pages/Comments.tsx b/packages/berlin/src/pages/Comments.tsx index c3323ccb..92412326 100644 --- a/packages/berlin/src/pages/Comments.tsx +++ b/packages/berlin/src/pages/Comments.tsx @@ -99,13 +99,14 @@ function Comments() { useEffect(() => { if (optionId) { + const sumOfAllVotes = userVotes?.reduce((acc, option) => acc + option.numOfVotes, 0) || 0; const hearts = userVotes?.find((option) => optionId === option.optionId)?.numOfVotes || 0; setLocalOptionHearts(hearts); setLocalUserVotes([{ optionId: optionId, numOfVotes: hearts }]); // update the available hearts setAvailableHearts({ questionId: option?.questionId ?? '', - hearts: INITIAL_HEARTS - hearts, + hearts: INITIAL_HEARTS - sumOfAllVotes, }); } }, [optionId, userVotes, setAvailableHearts, option?.questionId]); From 43a843ed3f44af69775e1bcc100f105b68ff9e6a Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 16 May 2024 18:13:58 +0100 Subject: [PATCH 3/3] consider less than 0 available hearts --- packages/berlin/src/pages/Comments.tsx | 2 +- packages/berlin/src/pages/Cycle.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/berlin/src/pages/Comments.tsx b/packages/berlin/src/pages/Comments.tsx index 92412326..92abad66 100644 --- a/packages/berlin/src/pages/Comments.tsx +++ b/packages/berlin/src/pages/Comments.tsx @@ -106,7 +106,7 @@ function Comments() { // update the available hearts setAvailableHearts({ questionId: option?.questionId ?? '', - hearts: INITIAL_HEARTS - sumOfAllVotes, + hearts: Math.max(0, INITIAL_HEARTS - sumOfAllVotes), }); } }, [optionId, userVotes, setAvailableHearts, option?.questionId]); diff --git a/packages/berlin/src/pages/Cycle.tsx b/packages/berlin/src/pages/Cycle.tsx index 96a34a06..e5647e9f 100644 --- a/packages/berlin/src/pages/Cycle.tsx +++ b/packages/berlin/src/pages/Cycle.tsx @@ -102,7 +102,7 @@ function Cycle() { setAvailableHearts({ questionId: cycle?.forumQuestions[0].id || '', - hearts: INITIAL_HEARTS - givenVotes, + hearts: Math.max(0, INITIAL_HEARTS - givenVotes), }); setLocalUserVotes(votes);