handleDownvote(questionId as string)}
- onUpvote={() => handleUpvote(questionId as string)}
{...question}
- receivedCount={0} // TODO: Change to actual value
- showVoteButtons={true}
+ questionId={question.id}
+ receivedCount={0}
timestamp={question.seenAt.toLocaleDateString(undefined, {
month: 'short',
year: 'numeric',
@@ -178,8 +173,9 @@ export default function QuestionPage() {
{(comments ?? []).map((comment) => (
- (
{
if (areFiltersInitialized) {
// Router.replace used instead of router.replace to avoid
@@ -293,6 +291,7 @@ export default function QuestionsHomePage() {
question.content,
)}`}
location={question.location}
+ questionId={question.id}
receivedCount={0}
role={question.role}
timestamp={question.seenAt.toLocaleDateString(undefined, {
@@ -301,9 +300,6 @@ export default function QuestionsHomePage() {
})}
type={question.type} // TODO: Implement received count
upvoteCount={question.numVotes}
- voteState={voteState}
- onDownvote={() => handleDownvote(question.id)}
- onUpvote={() => handleUpvote(question.id)}
/>
))}
{questions?.length === 0 && (
diff --git a/apps/portal/src/server/router/questions-answer-comment-router.ts b/apps/portal/src/server/router/questions-answer-comment-router.ts
index f1863f03..f75195c7 100644
--- a/apps/portal/src/server/router/questions-answer-comment-router.ts
+++ b/apps/portal/src/server/router/questions-answer-comment-router.ts
@@ -184,7 +184,7 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
},
});
- if (voteToUpdate?.id !== userId) {
+ if (voteToUpdate?.userId !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
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({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
diff --git a/apps/portal/src/server/router/questions-answer-router.ts b/apps/portal/src/server/router/questions-answer-router.ts
index f2d078df..7dadbeb4 100644
--- a/apps/portal/src/server/router/questions-answer-router.ts
+++ b/apps/portal/src/server/router/questions-answer-router.ts
@@ -245,7 +245,7 @@ export const questionsAnswerRouter = createProtectedRouter()
},
});
- if (voteToUpdate?.id !== userId) {
+ if (voteToUpdate?.userId !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
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({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
diff --git a/apps/portal/src/server/router/questions-question-comment-router.ts b/apps/portal/src/server/router/questions-question-comment-router.ts
index c285571e..f3985f31 100644
--- a/apps/portal/src/server/router/questions-question-comment-router.ts
+++ b/apps/portal/src/server/router/questions-question-comment-router.ts
@@ -183,7 +183,7 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
},
});
- if (voteToUpdate?.id !== userId) {
+ if (voteToUpdate?.userId !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
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({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
diff --git a/apps/portal/src/server/router/questions-question-router.ts b/apps/portal/src/server/router/questions-question-router.ts
index 39dafd98..2cb564d3 100644
--- a/apps/portal/src/server/router/questions-question-router.ts
+++ b/apps/portal/src/server/router/questions-question-router.ts
@@ -335,7 +335,7 @@ export const questionsQuestionRouter = createProtectedRouter()
},
});
- if (voteToUpdate?.id !== userId) {
+ if (voteToUpdate?.userId !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
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({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
diff --git a/apps/portal/src/utils/questions/useVote.ts b/apps/portal/src/utils/questions/useVote.ts
index 6b821c61..e9ae6466 100644
--- a/apps/portal/src/utils/questions/useVote.ts
+++ b/apps/portal/src/utils/questions/useVote.ts
@@ -1,19 +1,165 @@
-import { useState } from 'react';
+import { useCallback } from 'react';
+import type { Vote } from '@prisma/client';
-export const enum VoteState {
- NO_VOTE,
- UPVOTE,
- DOWNVOTE,
-}
+import { trpc } from '../trpc';
-export const useVote = () => {
- const [voteState, setVoteState] = useState(VoteState.NO_VOTE);
+type UseVoteOptions = {
+ 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 = (id: string, voteState) => {
- //
+ 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 = (id: string) => {};
+ 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 };
+};
+
+type MutationKey = Parameters[0];
+type QueryKey = Parameters[0][0];
+
+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',
+ });
+};
+
+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 = {
+ create: MutationKey;
+ deleteKey: MutationKey;
+ idKey: string;
+ query: VoteQueryKey;
+ update: MutationKey;
+};
+
+export const useVote = (
+ id: string,
+ opts: VoteProps,
+) => {
+ 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 };
};