diff --git a/packages/api/src/types/OptionUsersType.ts b/packages/api/src/types/OptionUsersType.ts index 8c5a2fa9..b2d844a4 100644 --- a/packages/api/src/types/OptionUsersType.ts +++ b/packages/api/src/types/OptionUsersType.ts @@ -1,11 +1,23 @@ -import { GetGroupsResponse } from './GroupType'; -import { GetUserResponse } from './UserType'; - export type GetOptionUsersResponse = { optionId: string; registrationId: string | null; userId: string | null; - user: GetUserResponse | null; + user: { + id: string; + username: string; + firstName: string; + lastName: string; + } | null; groupId: string | null; - group: GetGroupsResponse | null; + group: { + id: null; + users: + | { + id: string; + username: string; + firstName: string; + lastName: string; + }[] + | null; + } | null; }; 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 d0541b95..b0c376bb 100644 --- a/packages/berlin/src/pages/Comments.tsx +++ b/packages/berlin/src/pages/Comments.tsx @@ -11,14 +11,19 @@ import { fetchUserVotes, fetchComments, postComment, - // fetchOptionUsers, + fetchOptionUsers, } 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'; @@ -40,15 +45,16 @@ import CommentsColumns from '../components/columns/comments-columns'; import IconButton from '../components/icon-button'; import Textarea from '../components/textarea'; +type LocalUserVotes = ResponseUserVotesType | { optionId: string; numOfVotes: number }[]; + function Comments() { const theme = useAppStore((state) => state.theme); const queryClient = useQueryClient(); const { cycleId, optionId } = useParams(); const { user } = useUser(); - const { availableHearts, setAvailableHearts } = useAppStore((state) => state); - const [localUserVotes, setLocalUserVotes] = useState< - ResponseUserVotesType | { optionId: string; numOfVotes: number }[] - >([]); + 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(''); const [sortOrder, setSortOrder] = useState('desc'); // 'asc' for ascending, 'desc' for descending @@ -66,17 +72,17 @@ function Comments() { retry: false, }); - // const { data: optionUsers } = useQuery({ - // queryKey: ['optionUsers', optionId], - // queryFn: () => fetchOptionUsers(optionId || ''), - // enabled: !!optionId, - // }); + const { data: optionUsers } = useQuery({ + 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 + // refetchInterval: 5000, // Poll every 5 seconds }); const sortedComments = useMemo(() => { @@ -97,52 +103,45 @@ function Comments() { } }, [optionId, userVotes]); - const { mutate: mutateVotes } = useMutation({ - mutationFn: postVotes, - onSuccess: (body) => { - if (body?.errors?.length) { - toast.error(`Failed to save votes, ${body?.errors[0].message}`); - } else if (body?.data.length) { - queryClient.invalidateQueries({ queryKey: ['votes', cycleId] }); - // this is to update the plural scores in each option - queryClient.invalidateQueries({ queryKey: ['cycles', cycleId] }); - toast.success('Votes saved successfully!'); - } - }, - }); - const { mutate: mutateComments } = useMutation({ 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 handleSaveVotesWrapper = () => { + const { mutate: mutateVotes } = useMutation({ + mutationFn: postVotes, + onSuccess: (body) => { + if (body?.errors?.length) { + toast.error(`Failed to save votes, ${body?.errors[0].message}`); + } else if (body?.data.length) { + queryClient.invalidateQueries({ queryKey: ['votes', cycleId] }); + // this is to update the plural scores in each option + queryClient.invalidateQueries({ queryKey: ['cycles', cycleId] }); + toast.success('Votes saved successfully!'); + } + }, + }); + + const handleSaveVoteWrapper = () => { handleSaveVotes(userVotes, localUserVotes, mutateVotes); }; - const votesAreDifferent = useMemo(() => { - if (localUserVotes && userVotes) { - return ( - localUserVotes[0]?.numOfVotes !== - userVotes?.find((vote) => vote.optionId === optionId)?.numOfVotes - ); - } - }, [localUserVotes, optionId, userVotes]); - const handlePostComment = () => { if (optionId && comment) { mutateComments({ questionOptionId: optionId, value: comment }); @@ -173,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} @@ -183,17 +182,18 @@ function Comments() { {option?.optionTitle} {option?.optionSubTitle} - {/* - Lead author: {option?.user} [// TODO] - - Co-authors: [// TODO] - */} + Lead author: {optionUsers?.user?.firstName} {optionUsers?.user?.lastName} + + {optionUsers?.group?.users && ( + + Co-authors:{' '} + {optionUsers.group.users.map((user) => `${(user.firstName, user.lastName)}`)} + + )} - +