From 21124d5a37d1b3ef8eacfbdc76b0cb8bd4a7c6bc Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 16 May 2024 09:35:22 +0100 Subject: [PATCH] simplify and refactor comments page and voting mechanism --- .../berlin/src/components/header/Header.tsx | 6 +- .../src/components/option-card/OptionCard.tsx | 6 +- packages/berlin/src/pages/Comments.tsx | 27 +++-- packages/berlin/src/pages/Cycle.tsx | 17 ++- packages/berlin/src/utils/voting.ts | 105 ++++++++---------- 5 files changed, 79 insertions(+), 82 deletions(-) diff --git a/packages/berlin/src/components/header/Header.tsx b/packages/berlin/src/components/header/Header.tsx index 1123443e..f51d1675 100644 --- a/packages/berlin/src/components/header/Header.tsx +++ b/packages/berlin/src/components/header/Header.tsx @@ -113,14 +113,14 @@ function Header() { ) ); })} + + My proposals + Agenda )} - - My proposals - navigate('/account')} diff --git a/packages/berlin/src/components/option-card/OptionCard.tsx b/packages/berlin/src/components/option-card/OptionCard.tsx index 70d32669..d46d441b 100644 --- a/packages/berlin/src/components/option-card/OptionCard.tsx +++ b/packages/berlin/src/components/option-card/OptionCard.tsx @@ -12,9 +12,9 @@ type OptionCardProps = { option: QuestionOption; numOfVotes: number; onVote: () => void; - onUnvote: () => void; + onUnVote: () => void; }; -function OptionCard({ option, numOfVotes, onVote, onUnvote }: OptionCardProps) { +function OptionCard({ option, numOfVotes, onVote, onUnVote }: OptionCardProps) { const { eventId, cycleId } = useParams(); const theme = useAppStore((state) => state.theme); const navigate = useNavigate(); @@ -67,7 +67,7 @@ function OptionCard({ option, numOfVotes, onVote, onUnvote }: OptionCardProps) { $padding={0} $color="secondary" icon={{ src: `/icons/downvote-${theme}.svg`, alt: 'Downvote arrow' }} - onClick={onUnvote} + onClick={onUnVote} $width={16} $height={16} disabled={numOfVotes === 0} diff --git a/packages/berlin/src/pages/Comments.tsx b/packages/berlin/src/pages/Comments.tsx index e2b2d948..b0c376bb 100644 --- a/packages/berlin/src/pages/Comments.tsx +++ b/packages/berlin/src/pages/Comments.tsx @@ -12,14 +12,18 @@ import { fetchComments, postComment, fetchOptionUsers, - fetchCycle, } from 'api'; // Hooks import useUser from '../hooks/useUser'; // Utils -import { handleSaveVotes, handleUnvote, handleVote } from '../utils/voting'; +import { + handleSaveVotes, + handleAvailableHearts, + handleLocalUnVote, + handleLocalVote, +} from '../utils/voting'; // Types import { ResponseUserVotesType } from '../types/CycleType'; @@ -48,7 +52,8 @@ function Comments() { const queryClient = useQueryClient(); const { cycleId, optionId } = useParams(); const { user } = useUser(); - const { availableHearts, setAvailableHearts } = useAppStore((state) => state); + 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(''); @@ -68,13 +73,13 @@ function Comments() { }); const { data: optionUsers } = useQuery({ - queryKey: ['optionUsers', optionId], + queryKey: ['option', optionId, 'users'], queryFn: () => fetchOptionUsers(optionId || ''), enabled: !!optionId, }); const { data: comments } = useQuery({ - queryKey: ['comments', optionId], + queryKey: ['option', optionId, 'comments'], queryFn: () => fetchComments({ optionId: optionId || '' }), enabled: !!optionId, // refetchInterval: 5000, // Poll every 5 seconds @@ -102,19 +107,21 @@ function Comments() { mutationFn: postComment, onSuccess: (body) => { if (body?.value) { - queryClient.invalidateQueries({ queryKey: ['comments', optionId] }); + queryClient.invalidateQueries({ queryKey: ['option', optionId, 'comments'] }); } }, }); const handleVoteWrapper = (optionId: string) => { setLocalOptionHearts((prevLocalOptionHearts) => prevLocalOptionHearts + 1); - handleVote(optionId, availableHearts, setAvailableHearts, setLocalUserVotes); + setLocalUserVotes((prevLocalUserVotes) => handleLocalVote(optionId, prevLocalUserVotes)); + setAvailableHearts(handleAvailableHearts(availableHearts, 'vote')); }; - const handleUnvoteWrapper = (optionId: string) => { + const handleUnVoteWrapper = (optionId: string) => { setLocalOptionHearts((prevLocalOptionHearts) => Math.max(0, prevLocalOptionHearts - 1)); - handleUnvote(optionId, availableHearts, setAvailableHearts, setLocalUserVotes); + setLocalUserVotes((prevLocalUserVotes) => handleLocalUnVote(optionId, prevLocalUserVotes)); + setAvailableHearts(handleAvailableHearts(availableHearts, 'unVote')); }; const { mutate: mutateVotes } = useMutation({ @@ -165,7 +172,7 @@ function Comments() { $padding={0} $color="secondary" icon={{ src: `/icons/downvote-${theme}.svg`, alt: 'Downvote arrow' }} - onClick={() => handleUnvoteWrapper(option?.id ?? '')} + onClick={() => handleUnVoteWrapper(option?.id ?? '')} $width={16} $height={16} disabled={localOptionHearts === 0} diff --git a/packages/berlin/src/pages/Cycle.tsx b/packages/berlin/src/pages/Cycle.tsx index 3c627a7c..04d93b3e 100644 --- a/packages/berlin/src/pages/Cycle.tsx +++ b/packages/berlin/src/pages/Cycle.tsx @@ -12,7 +12,12 @@ import useCountdown from '../hooks/useCountdown'; import useUser from '../hooks/useUser'; // Utils -import { handleSaveVotes, handleUnvote, handleVote } from '../utils/voting'; +import { + handleSaveVotes, + handleAvailableHearts, + handleLocalUnVote, + handleLocalVote, +} from '../utils/voting'; // Types import { ResponseUserVotesType } from '../types/CycleType'; @@ -137,11 +142,13 @@ function Cycle() { }); const handleVoteWrapper = (optionId: string) => { - handleVote(optionId, availableHearts, setAvailableHearts, setLocalUserVotes); + setLocalUserVotes((prevLocalUserVotes) => handleLocalVote(optionId, prevLocalUserVotes)); + setAvailableHearts(handleAvailableHearts(availableHearts, 'vote')); }; - const handleUnvoteWrapper = (optionId: string) => { - handleUnvote(optionId, availableHearts, setAvailableHearts, setLocalUserVotes); + const handleUnVoteWrapper = (optionId: string) => { + setLocalUserVotes((prevLocalUserVotes) => handleLocalUnVote(optionId, prevLocalUserVotes)); + setAvailableHearts(handleAvailableHearts(availableHearts, 'unVote')); }; const handleSaveVotesWrapper = () => { @@ -243,7 +250,7 @@ function Cycle() { option={option} numOfVotes={numOfVotes} onVote={() => handleVoteWrapper(option.id)} - onUnvote={() => handleUnvoteWrapper(option.id)} + onUnVote={() => handleUnVoteWrapper(option.id)} /> ); })} diff --git a/packages/berlin/src/utils/voting.ts b/packages/berlin/src/utils/voting.ts index cc824c03..7fa878ec 100644 --- a/packages/berlin/src/utils/voting.ts +++ b/packages/berlin/src/utils/voting.ts @@ -2,65 +2,51 @@ import { GetUserVotesResponse, PostVotesRequest } from 'api'; import { ResponseUserVotesType } from '../types/CycleType'; import toast from 'react-hot-toast'; -export const handleVote = ( +export const handleLocalVote = ( optionId: string, - availableHearts: number, - setAvailableHearts: (hearts: number) => void, - setLocalUserVotes: React.Dispatch< - React.SetStateAction< - | ResponseUserVotesType - | { - optionId: string; - numOfVotes: number; - }[] - > - >, + prevLocalUserVotes: ResponseUserVotesType | { optionId: string; numOfVotes: number }[], ) => { - if (availableHearts > 0) { - setLocalUserVotes((prevLocalUserVotes) => { - const temp = prevLocalUserVotes.find((x) => x.optionId === optionId); - if (!temp) { - return [...prevLocalUserVotes, { optionId, numOfVotes: 1 }]; - } - const updatedLocalVotes = prevLocalUserVotes.map((prevLocalUserVote) => { - if (prevLocalUserVote.optionId === optionId) { - return { ...prevLocalUserVote, numOfVotes: prevLocalUserVote.numOfVotes + 1 }; - } - return prevLocalUserVote; - }); - return updatedLocalVotes; - }); - setAvailableHearts(Math.max(0, availableHearts - 1)); + // find if the user has already voted for this option + const prevVote = prevLocalUserVotes.find((x) => x.optionId === optionId); + if (!prevVote) { + // if the user has not voted for this option, add a new vote + return [...prevLocalUserVotes, { optionId, numOfVotes: 1 }]; } + + // if the user has already voted for this option, update the number of votes + // this will just find the option and increase the number of votes by 1 + const updatedLocalVotes = prevLocalUserVotes.map((prevLocalUserVote) => { + if (prevLocalUserVote.optionId === optionId) { + return { ...prevLocalUserVote, numOfVotes: prevLocalUserVote.numOfVotes + 1 }; + } + return prevLocalUserVote; + }); + + return updatedLocalVotes; }; -export const handleUnvote = ( +export const handleLocalUnVote = ( optionId: string, - availableHearts: number, - setAvailableHearts: (hearts: number) => void, - setLocalUserVotes: React.Dispatch< - React.SetStateAction< - | ResponseUserVotesType - | { - optionId: string; - numOfVotes: number; - }[] - > - >, + prevLocalUserVotes: ResponseUserVotesType | { optionId: string; numOfVotes: number }[], ) => { - setLocalUserVotes((prevLocalUserVotes) => { - const updatedLocalVotes = prevLocalUserVotes.map((prevLocalUserVote) => { - if (prevLocalUserVote.optionId === optionId) { - const newNumOfVotes = Math.max(0, prevLocalUserVote.numOfVotes - 1); - return { ...prevLocalUserVote, numOfVotes: newNumOfVotes }; - } - return prevLocalUserVote; - }); - - return updatedLocalVotes; + // this will just find the option and decrease the number of votes by 1 + const updatedLocalVotes = prevLocalUserVotes.map((prevLocalUserVote) => { + if (prevLocalUserVote.optionId === optionId) { + const newNumOfVotes = Math.max(0, prevLocalUserVote.numOfVotes - 1); + return { ...prevLocalUserVote, numOfVotes: newNumOfVotes }; + } + return prevLocalUserVote; }); - setAvailableHearts(Math.min(20, availableHearts + 1)); + return updatedLocalVotes; +}; + +export const handleAvailableHearts = (availableHearts: number, type: 'vote' | 'unVote') => { + if (type === 'vote') { + return Math.max(0, availableHearts - 1); + } + + return Math.min(20, availableHearts + 1); }; export const handleSaveVotes = ( @@ -82,18 +68,15 @@ export const handleSaveVotes = ( for (const localVote of localUserVotes) { const matchingServerVote = serverVotesMap.get(localVote.optionId); - - if (!matchingServerVote) { - mutateVotesReq.votes.push({ - optionId: localVote.optionId, - numOfVotes: localVote.numOfVotes, - }); - } else if (matchingServerVote.numOfVotes !== localVote.numOfVotes) { - mutateVotesReq.votes.push({ - optionId: localVote.optionId, - numOfVotes: localVote.numOfVotes, - }); + // if the vote is the same as the server vote, we don't need to send it + if (matchingServerVote && matchingServerVote.numOfVotes === localVote.numOfVotes) { + continue; } + + mutateVotesReq.votes.push({ + optionId: localVote.optionId, + numOfVotes: localVote.numOfVotes, + }); } mutateVotes(mutateVotesReq);