[questions][feat] add upvote, downvotes

pull/355/head
Jeff Sieu 3 years ago
parent 18a9f62b12
commit 452bd8e27e

@ -1,8 +1,11 @@
import { format } from 'date-fns'; import { format } from 'date-fns';
import { useAnswerCommentVote } from '~/utils/questions/useVote';
import VotingButtons from './VotingButtons'; import VotingButtons from './VotingButtons';
export type CommentListItemProps = { export type AnswerCommentListItemProps = {
answerCommentId: string;
authorImageUrl: string; authorImageUrl: string;
authorName: string; authorName: string;
content: string; content: string;
@ -10,16 +13,26 @@ export type CommentListItemProps = {
upvoteCount: number; upvoteCount: number;
}; };
export default function CommentListItem({ export default function AnswerCommentListItem({
authorImageUrl, authorImageUrl,
authorName, authorName,
content, content,
createdAt, createdAt,
upvoteCount, upvoteCount,
}: CommentListItemProps) { answerCommentId,
}: AnswerCommentListItemProps) {
const { handleDownvote, handleUpvote, vote } =
useAnswerCommentVote(answerCommentId);
return ( return (
<div className="flex gap-4 border bg-white p-2 "> <div className="flex gap-4 border bg-white p-2 ">
<VotingButtons size="sm" upvoteCount={upvoteCount} /> <VotingButtons
size="sm"
upvoteCount={upvoteCount}
vote={vote}
onDownvote={handleDownvote}
onUpvote={handleUpvote}
/>
<div className="mt-1 flex flex-col gap-1"> <div className="mt-1 flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<img <img

@ -1,14 +1,18 @@
import React from 'react'; import React from 'react';
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
import type { Vote } from '@prisma/client';
import type { ButtonSize } from '@tih/ui'; import type { ButtonSize } from '@tih/ui';
import { Button } from '@tih/ui'; import { Button } from '@tih/ui';
import { VoteState } from '~/utils/questions/useVote'; export type BackendVote = {
id: string;
vote: Vote;
};
export type VotingButtonsCallbackProps = { export type VotingButtonsCallbackProps = {
onDownvote: () => void; onDownvote: () => void;
onUpvote: () => void; onUpvote: () => void;
voteState: VoteState; vote: BackendVote | null;
}; };
export type VotingButtonsProps = VotingButtonsCallbackProps & { export type VotingButtonsProps = VotingButtonsCallbackProps & {
@ -17,16 +21,16 @@ export type VotingButtonsProps = VotingButtonsCallbackProps & {
}; };
export default function VotingButtons({ export default function VotingButtons({
voteState, vote,
onDownvote, onDownvote,
onUpvote, onUpvote,
upvoteCount, upvoteCount,
size = 'md', size = 'md',
}: VotingButtonsProps) { }: VotingButtonsProps) {
const upvoteButtonVarient = const upvoteButtonVariant =
voteState === VoteState.UPVOTE ? 'secondary' : 'tertiary'; vote?.vote === 'UPVOTE' ? 'secondary' : 'tertiary';
const downvoteButtonVarient = const downvoteButtonVariant =
voteState === VoteState.DOWNVOTE ? 'secondary' : 'tertiary'; vote?.vote === 'DOWNVOTE' ? 'secondary' : 'tertiary';
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Button <Button
@ -34,7 +38,7 @@ export default function VotingButtons({
isLabelHidden={true} isLabelHidden={true}
label="Upvote" label="Upvote"
size={size} size={size}
variant={upvoteButtonVarient} variant={upvoteButtonVariant}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -47,7 +51,7 @@ export default function VotingButtons({
isLabelHidden={true} isLabelHidden={true}
label="Downvote" label="Downvote"
size={size} size={size}
variant={downvoteButtonVarient} variant={downvoteButtonVariant}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();

@ -1,13 +1,13 @@
import { format } from 'date-fns'; import { format } from 'date-fns';
import { ChatBubbleLeftRightIcon } from '@heroicons/react/24/outline'; import { ChatBubbleLeftRightIcon } from '@heroicons/react/24/outline';
import type { import { useAnswerVote } from '~/utils/questions/useVote';
VotingButtonsCallbackProps,
VotingButtonsProps, import type { VotingButtonsProps } from '../VotingButtons';
} from '../VotingButtons';
import VotingButtons from '../VotingButtons'; import VotingButtons from '../VotingButtons';
export type AnswerCardProps = VotingButtonsCallbackProps & { export type AnswerCardProps = {
answerId: string;
authorImageUrl: string; authorImageUrl: string;
authorName: string; authorName: string;
commentCount?: number; commentCount?: number;
@ -18,25 +18,24 @@ export type AnswerCardProps = VotingButtonsCallbackProps & {
}; };
export default function AnswerCard({ export default function AnswerCard({
voteState, answerId,
onDownvote,
onUpvote,
authorName, authorName,
authorImageUrl, authorImageUrl,
upvoteCount,
content, content,
createdAt, createdAt,
commentCount, commentCount,
votingButtonsSize, votingButtonsSize,
upvoteCount,
}: AnswerCardProps) { }: AnswerCardProps) {
const { handleUpvote, handleDownvote, vote } = useAnswerVote(answerId);
return ( return (
<article className="flex gap-4 rounded-md border bg-white p-2"> <article className="flex gap-4 rounded-md border bg-white p-2">
<VotingButtons <VotingButtons
size={votingButtonsSize} size={votingButtonsSize}
upvoteCount={upvoteCount} upvoteCount={upvoteCount}
voteState={voteState} vote={vote}
onDownvote={onDownvote} onDownvote={handleDownvote}
onUpvote={onUpvote} onUpvote={handleUpvote}
/> />
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

@ -1,72 +1,26 @@
import type { QuestionsQuestionType } from '@prisma/client'; import type { QuestionCardProps } from './QuestionCard';
import { Badge } from '@tih/ui'; import QuestionCard from './QuestionCard';
import QuestionTypeBadge from '../QuestionTypeBadge'; export type QuestionOverviewCardProps = Omit<
import type { VotingButtonsCallbackProps } from '../VotingButtons'; QuestionCardProps & {
import VotingButtons from '../VotingButtons'; showActionButton: false;
showUserStatistics: false;
type UpvoteProps =
| {
showVoteButtons: true; showVoteButtons: true;
upvoteCount: number; },
} | 'actionButtonLabel'
| { | 'onActionButtonClick'
showVoteButtons?: false; | 'showActionButton'
upvoteCount?: never; | 'showUserStatistics'
}; | 'showVoteButtons'
>;
export type FullQuestionCardProps = UpvoteProps &
VotingButtonsCallbackProps & {
company: string;
content: string;
location: string;
receivedCount: number;
role: string;
timestamp: string;
type: QuestionsQuestionType;
};
export default function FullQuestionCard({ export default function FullQuestionCard(props: QuestionOverviewCardProps) {
voteState,
onDownvote,
onUpvote,
company,
content,
showVoteButtons,
upvoteCount,
timestamp,
role,
location,
type,
}: FullQuestionCardProps) {
const altText = `${company} logo`;
return ( return (
<article className="flex gap-4 rounded-md border border-slate-300 bg-white p-4"> <QuestionCard
{showVoteButtons && ( {...props}
<VotingButtons showActionButton={false}
upvoteCount={upvoteCount} showUserStatistics={false}
voteState={voteState} showVoteButtons={true}
onDownvote={onDownvote}
onUpvote={onUpvote}
/> />
)}
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<img alt={altText} src="https://logo.clearbit.com/google.com"></img>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2 text-slate-500">
<Badge label={company} variant="primary" />
<QuestionTypeBadge type={type} />
<p className="text-xs">
{timestamp} · {location} · {role}
</p>
</div>
</div>
<div className="mx-2 mb-2">
<p>{content}</p>
</div>
</div>
</article>
); );
} }

@ -2,8 +2,9 @@ import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline';
import type { QuestionsQuestionType } from '@prisma/client'; import type { QuestionsQuestionType } from '@prisma/client';
import { Badge, Button } from '@tih/ui'; import { Badge, Button } from '@tih/ui';
import { useQuestionVote } from '~/utils/questions/useVote';
import QuestionTypeBadge from '../QuestionTypeBadge'; import QuestionTypeBadge from '../QuestionTypeBadge';
import type { VotingButtonsCallbackProps } from '../VotingButtons';
import VotingButtons from '../VotingButtons'; import VotingButtons from '../VotingButtons';
type UpvoteProps = type UpvoteProps =
@ -40,12 +41,11 @@ type ActionButtonProps =
export type QuestionCardProps = ActionButtonProps & export type QuestionCardProps = ActionButtonProps &
StatisticsProps & StatisticsProps &
UpvoteProps & UpvoteProps & {
VotingButtonsCallbackProps & {
company: string; company: string;
content: string; content: string;
href?: string;
location: string; location: string;
questionId: string;
receivedCount: number; receivedCount: number;
role: string; role: string;
timestamp: string; timestamp: string;
@ -53,9 +53,7 @@ export type QuestionCardProps = ActionButtonProps &
}; };
export default function QuestionCard({ export default function QuestionCard({
voteState, questionId,
onDownvote,
onUpvote,
company, company,
answerCount, answerCount,
content, content,
@ -71,14 +69,16 @@ export default function QuestionCard({
role, role,
location, location,
}: QuestionCardProps) { }: QuestionCardProps) {
const { handleDownvote, handleUpvote, vote } = useQuestionVote(questionId);
return ( return (
<article className="flex gap-4 rounded-md border border-slate-300 bg-white p-4 hover:bg-slate-50"> <article className="flex gap-4 rounded-md border border-slate-300 bg-white p-4">
{showVoteButtons && ( {showVoteButtons && (
<VotingButtons <VotingButtons
upvoteCount={upvoteCount} upvoteCount={upvoteCount}
voteState={voteState} vote={vote}
onDownvote={onDownvote} onDownvote={handleDownvote}
onUpvote={onUpvote} onUpvote={handleUpvote}
/> />
)} )}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">

@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form';
import { ArrowSmallLeftIcon } from '@heroicons/react/24/outline'; import { ArrowSmallLeftIcon } from '@heroicons/react/24/outline';
import { Button, Select, TextArea } from '@tih/ui'; import { Button, Select, TextArea } from '@tih/ui';
import AnswerCommentListItem from '~/components/questions/AnswerCommentListItem';
import FullAnswerCard from '~/components/questions/card/FullAnswerCard'; import FullAnswerCard from '~/components/questions/card/FullAnswerCard';
import CommentListItem from '~/components/questions/CommentListItem';
import FullScreenSpinner from '~/components/questions/FullScreenSpinner'; import FullScreenSpinner from '~/components/questions/FullScreenSpinner';
import { useFormRegister } from '~/utils/questions/useFormRegister'; import { useFormRegister } from '~/utils/questions/useFormRegister';
@ -81,6 +81,7 @@ export default function QuestionPage() {
<div className="flex w-full justify-center overflow-y-auto py-4 px-5"> <div className="flex w-full justify-center overflow-y-auto py-4 px-5">
<div className="flex max-w-7xl flex-1 flex-col gap-2"> <div className="flex max-w-7xl flex-1 flex-col gap-2">
<FullAnswerCard <FullAnswerCard
answerId={answer.id}
authorImageUrl={answer.userImage} authorImageUrl={answer.userImage}
authorName={answer.user} authorName={answer.user}
content={answer.content} content={answer.content}
@ -138,8 +139,9 @@ export default function QuestionPage() {
</form> </form>
{(comments ?? []).map((comment) => ( {(comments ?? []).map((comment) => (
<CommentListItem <AnswerCommentListItem
key={comment.id} key={comment.id}
answerCommentId={comment.id}
authorImageUrl={comment.userImage} authorImageUrl={comment.userImage}
authorName={comment.user} authorName={comment.user}
content={comment.content} content={comment.content}

@ -3,14 +3,13 @@ import { useForm } from 'react-hook-form';
import { ArrowSmallLeftIcon } from '@heroicons/react/24/outline'; import { ArrowSmallLeftIcon } from '@heroicons/react/24/outline';
import { Button, Collapsible, Select, TextArea } from '@tih/ui'; import { Button, Collapsible, Select, TextArea } from '@tih/ui';
import AnswerCommentListItem from '~/components/questions/AnswerCommentListItem';
import FullQuestionCard from '~/components/questions/card/FullQuestionCard'; import FullQuestionCard from '~/components/questions/card/FullQuestionCard';
import QuestionAnswerCard from '~/components/questions/card/QuestionAnswerCard'; import QuestionAnswerCard from '~/components/questions/card/QuestionAnswerCard';
import CommentListItem from '~/components/questions/CommentListItem';
import FullScreenSpinner from '~/components/questions/FullScreenSpinner'; import FullScreenSpinner from '~/components/questions/FullScreenSpinner';
import createSlug from '~/utils/questions/createSlug'; import createSlug from '~/utils/questions/createSlug';
import { useFormRegister } from '~/utils/questions/useFormRegister'; import { useFormRegister } from '~/utils/questions/useFormRegister';
import { useVote } from '~/utils/questions/useVote';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
export type AnswerQuestionData = { export type AnswerQuestionData = {
@ -40,7 +39,6 @@ export default function QuestionPage() {
const commentRegister = useFormRegister(comRegister); const commentRegister = useFormRegister(comRegister);
const { questionId } = router.query; const { questionId } = router.query;
const [handleUpvote, handleDownvote, voteState] = useVote();
const { data: question } = trpc.useQuery([ const { data: question } = trpc.useQuery([
'questions.questions.getQuestionById', 'questions.questions.getQuestionById',
@ -114,12 +112,9 @@ export default function QuestionPage() {
<div className="flex w-full justify-center overflow-y-auto py-4 px-5"> <div className="flex w-full justify-center overflow-y-auto py-4 px-5">
<div className="flex max-w-7xl flex-1 flex-col gap-2"> <div className="flex max-w-7xl flex-1 flex-col gap-2">
<FullQuestionCard <FullQuestionCard
voteState={voteState}
onDownvote={() => handleDownvote(questionId as string)}
onUpvote={() => handleUpvote(questionId as string)}
{...question} {...question}
receivedCount={0} // TODO: Change to actual value questionId={question.id}
showVoteButtons={true} receivedCount={0}
timestamp={question.seenAt.toLocaleDateString(undefined, { timestamp={question.seenAt.toLocaleDateString(undefined, {
month: 'short', month: 'short',
year: 'numeric', year: 'numeric',
@ -178,8 +173,9 @@ export default function QuestionPage() {
</form> </form>
{(comments ?? []).map((comment) => ( {(comments ?? []).map((comment) => (
<CommentListItem <AnswerCommentListItem
key={comment.id} key={comment.id}
answerCommentId={comment.id}
authorImageUrl={comment.userImage} authorImageUrl={comment.userImage}
authorName={comment.user} authorName={comment.user}
content={comment.content} content={comment.content}
@ -240,6 +236,7 @@ export default function QuestionPage() {
{(answers ?? []).map((answer) => ( {(answers ?? []).map((answer) => (
<QuestionAnswerCard <QuestionAnswerCard
key={answer.id} key={answer.id}
answerId={answer.id}
authorImageUrl={answer.userImage} authorImageUrl={answer.userImage}
authorName={answer.user} authorName={answer.user}
commentCount={answer.numComments} commentCount={answer.numComments}

@ -24,7 +24,6 @@ import {
useSearchFilter, useSearchFilter,
useSearchFilterSingle, useSearchFilterSingle,
} from '~/utils/questions/useSearchFilter'; } from '~/utils/questions/useSearchFilter';
import { useVote } from '~/utils/questions/useVote';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
export default function QuestionsHomePage() { export default function QuestionsHomePage() {
@ -145,7 +144,6 @@ export default function QuestionsHomePage() {
]); ]);
const { pathname } = router; const { pathname } = router;
const [handleDownvote, handleUpvote, voteState] = useVote();
useEffect(() => { useEffect(() => {
if (areFiltersInitialized) { if (areFiltersInitialized) {
// Router.replace used instead of router.replace to avoid // Router.replace used instead of router.replace to avoid
@ -293,6 +291,7 @@ export default function QuestionsHomePage() {
question.content, question.content,
)}`} )}`}
location={question.location} location={question.location}
questionId={question.id}
receivedCount={0} receivedCount={0}
role={question.role} role={question.role}
timestamp={question.seenAt.toLocaleDateString(undefined, { timestamp={question.seenAt.toLocaleDateString(undefined, {
@ -301,9 +300,6 @@ export default function QuestionsHomePage() {
})} })}
type={question.type} // TODO: Implement received count type={question.type} // TODO: Implement received count
upvoteCount={question.numVotes} upvoteCount={question.numVotes}
voteState={voteState}
onDownvote={() => handleDownvote(question.id)}
onUpvote={() => handleUpvote(question.id)}
/> />
))} ))}
{questions?.length === 0 && ( {questions?.length === 0 && (

@ -184,7 +184,7 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
}, },
}); });
if (voteToUpdate?.id !== userId) { if (voteToUpdate?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',
@ -215,7 +215,7 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
}, },
}); });
if (voteToDelete?.id !== userId) { if (voteToDelete?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',

@ -245,7 +245,7 @@ export const questionsAnswerRouter = createProtectedRouter()
}, },
}); });
if (voteToUpdate?.id !== userId) { if (voteToUpdate?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',
@ -275,7 +275,7 @@ export const questionsAnswerRouter = createProtectedRouter()
}, },
}); });
if (voteToDelete?.id !== userId) { if (voteToDelete?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',

@ -183,7 +183,7 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
}, },
}); });
if (voteToUpdate?.id !== userId) { if (voteToUpdate?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',
@ -214,7 +214,7 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
}, },
}); });
if (voteToDelete?.id !== userId) { if (voteToDelete?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',

@ -335,7 +335,7 @@ export const questionsQuestionRouter = createProtectedRouter()
}, },
}); });
if (voteToUpdate?.id !== userId) { if (voteToUpdate?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',
@ -365,7 +365,7 @@ export const questionsQuestionRouter = createProtectedRouter()
}, },
}); });
if (voteToDelete?.id !== userId) { if (voteToDelete?.userId !== userId) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
message: 'User have no authorization to record.', message: 'User have no authorization to record.',

@ -1,19 +1,165 @@
import { useState } from 'react'; import { useCallback } from 'react';
import type { Vote } from '@prisma/client';
export const enum VoteState { import { trpc } from '../trpc';
NO_VOTE,
UPVOTE, type UseVoteOptions = {
DOWNVOTE, createVote: (opts: { vote: Vote }) => void;
deleteVote: (opts: { id: string }) => void;
updateVote: (opts: BackendVote) => void;
};
type BackendVote = {
id: string;
vote: Vote;
};
const createVoteCallbacks = (
vote: BackendVote | null,
opts: UseVoteOptions,
) => {
const { createVote, updateVote, deleteVote } = opts;
const handleUpvote = () => {
// Either upvote or remove upvote
if (vote) {
if (vote.vote === 'DOWNVOTE') {
updateVote({
id: vote.id,
vote: 'UPVOTE',
});
} else {
deleteVote({
id: vote.id,
});
}
// Update vote to an upvote
} else {
createVote({
vote: 'UPVOTE',
});
} }
};
const handleDownvote = () => {
// Either downvote or remove downvote
if (vote) {
if (vote.vote === 'UPVOTE') {
updateVote({
id: vote.id,
vote: 'DOWNVOTE',
});
} else {
deleteVote({
id: vote.id,
});
}
// Update vote to an upvote
} else {
createVote({
vote: 'DOWNVOTE',
});
}
};
return { handleDownvote, handleUpvote };
};
export const useVote = () => { type MutationKey = Parameters<typeof trpc.useMutation>[0];
const [voteState, setVoteState] = useState(VoteState.NO_VOTE); type QueryKey = Parameters<typeof trpc.useQuery>[0][0];
const handleUpvote = (id: string, voteState) => { export const useQuestionVote = (id: string) => {
// return useVote(id, {
create: 'questions.questions.createVote',
deleteKey: 'questions.questions.deleteVote',
idKey: 'questionId',
query: 'questions.questions.getVote',
update: 'questions.questions.updateVote',
});
}; };
const handleDownvote = (id: string) => {}; export const useAnswerVote = (id: string) => {
return useVote(id, {
create: 'questions.answers.createVote',
deleteKey: 'questions.answers.deleteVote',
idKey: 'answerId',
query: 'questions.answers.getVote',
update: 'questions.answers.updateVote',
});
};
export const useQuestionCommentVote = (id: string) => {
return useVote(id, {
create: 'questions.questions.comments.createVote',
deleteKey: 'questions.questions.comments.deleteVote',
idKey: 'questionCommentId',
query: 'questions.questions.comments.getVote',
update: 'questions.questions.comments.updateVote',
});
};
export const useAnswerCommentVote = (id: string) => {
return useVote(id, {
create: 'questions.answers.comments.createVote',
deleteKey: 'questions.answers.comments.deleteVote',
idKey: 'answerCommentId',
query: 'questions.answers.comments.getVote',
update: 'questions.answers.comments.updateVote',
});
};
type VoteProps<VoteQueryKey extends QueryKey = QueryKey> = {
create: MutationKey;
deleteKey: MutationKey;
idKey: string;
query: VoteQueryKey;
update: MutationKey;
};
export const useVote = <VoteQueryKey extends QueryKey = QueryKey>(
id: string,
opts: VoteProps<VoteQueryKey>,
) => {
const { create, deleteKey, query, update, idKey } = opts;
const utils = trpc.useContext();
const onVoteUpdate = useCallback(() => {
utils.invalidateQueries([query, { [idKey]: id } as any]);
}, [id, idKey, utils, query]);
const { data } = trpc.useQuery([
query,
{
[idKey]: id,
},
] as any);
const backendVote = data as BackendVote;
const { mutate: createVote } = trpc.useMutation(create, {
onSuccess: onVoteUpdate,
});
const { mutate: updateVote } = trpc.useMutation(update, {
onSuccess: onVoteUpdate,
});
const { mutate: deleteVote } = trpc.useMutation(deleteKey, {
onSuccess: onVoteUpdate,
});
const { handleDownvote, handleUpvote } = createVoteCallbacks(
backendVote ?? null,
{
createVote: ({ vote }) => {
createVote({
[idKey]: id,
vote,
} as any);
},
deleteVote,
updateVote,
},
);
return [handleUpvote, handleDownvote, voteState] as const; return { handleDownvote, handleUpvote, vote: backendVote ?? null };
}; };

Loading…
Cancel
Save