+
+
+
{lists[selectedListIndex].questionEntries.map(
diff --git a/apps/portal/src/utils/questions/constants.ts b/apps/portal/src/utils/questions/constants.ts
index f09dd6f1..94f63c85 100644
--- a/apps/portal/src/utils/questions/constants.ts
+++ b/apps/portal/src/utils/questions/constants.ts
@@ -85,6 +85,21 @@ export const SORT_TYPES = [
},
];
+export const QUESTION_SORT_TYPES = [
+ {
+ label: 'New',
+ value: SortType.NEW,
+ },
+ {
+ label: 'Top',
+ value: SortType.TOP,
+ },
+ {
+ label: 'Encounters',
+ value: SortType.ENCOUNTERS,
+ },
+];
+
export const SAMPLE_QUESTION = {
answerCount: 10,
commentCount: 10,
diff --git a/apps/portal/src/utils/questions/mutations.ts b/apps/portal/src/utils/questions/mutations.ts
new file mode 100644
index 00000000..7fd1baf6
--- /dev/null
+++ b/apps/portal/src/utils/questions/mutations.ts
@@ -0,0 +1,60 @@
+import { trpc } from '../trpc';
+
+export function useAddQuestionToListAsync() {
+ const utils = trpc.useContext();
+ const { mutateAsync: addQuestionToListAsync } = trpc.useMutation(
+ 'questions.lists.createQuestionEntry',
+ {
+ // TODO: Add optimistic update
+ onSuccess: () => {
+ utils.invalidateQueries(['questions.lists.getListsByUser']);
+ },
+ },
+ );
+
+ return addQuestionToListAsync;
+}
+
+export function useRemoveQuestionFromListAsync() {
+ const utils = trpc.useContext();
+ const { mutateAsync: removeQuestionFromListAsync } = trpc.useMutation(
+ 'questions.lists.deleteQuestionEntry',
+ {
+ // TODO: Add optimistic update
+ onSuccess: () => {
+ utils.invalidateQueries(['questions.lists.getListsByUser']);
+ },
+ },
+ );
+
+ return removeQuestionFromListAsync;
+}
+
+export function useCreateListAsync() {
+ const utils = trpc.useContext();
+ const { mutateAsync: createListAsync } = trpc.useMutation(
+ 'questions.lists.create',
+ {
+ onSuccess: () => {
+ // TODO: Add optimistic update
+ utils.invalidateQueries(['questions.lists.getListsByUser']);
+ },
+ },
+ );
+ return createListAsync;
+}
+
+export function useDeleteListAsync() {
+ const utils = trpc.useContext();
+ const { mutateAsync: deleteListAsync } = trpc.useMutation(
+ 'questions.lists.delete',
+ {
+ onSuccess: () => {
+ // TODO: Add optimistic update
+ utils.invalidateQueries(['questions.lists.getListsByUser']);
+ },
+ },
+ );
+
+ return deleteListAsync;
+}
diff --git a/apps/portal/src/utils/questions/useVote.ts b/apps/portal/src/utils/questions/useVote.ts
index e35a608a..f1e8b864 100644
--- a/apps/portal/src/utils/questions/useVote.ts
+++ b/apps/portal/src/utils/questions/useVote.ts
@@ -1,9 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback } from 'react';
-import type { Vote } from '@prisma/client';
+import type { InfiniteData } from 'react-query';
+import { Vote } from '@prisma/client';
import { trpc } from '../trpc';
+import type { Question } from '~/types/questions';
+
type UseVoteOptions = {
setDownVote: () => void;
setNoVote: () => void;
@@ -46,12 +49,78 @@ type MutationKey = Parameters
[0];
type QueryKey = Parameters[0][0];
export const useQuestionVote = (id: string) => {
+ const utils = trpc.useContext();
+
return useVote(id, {
idKey: 'questionId',
invalidateKeys: [
- 'questions.questions.getQuestionsByFilter',
- 'questions.questions.getQuestionById',
+ // 'questions.questions.getQuestionById',
+ // 'questions.questions.getQuestionsByFilterAndContent',
],
+ onMutate: async (previousVote, currentVote) => {
+ const questionQueries = utils.queryClient.getQueriesData([
+ 'questions.questions.getQuestionsByFilterAndContent',
+ ]);
+
+ const getVoteValue = (vote: Vote | null) => {
+ if (vote === Vote.UPVOTE) {
+ return 1;
+ }
+ if (vote === Vote.DOWNVOTE) {
+ return -1;
+ }
+ return 0;
+ };
+
+ const voteValueChange =
+ getVoteValue(currentVote) - getVoteValue(previousVote);
+
+ for (const [key, query] of questionQueries) {
+ if (query === undefined) {
+ continue;
+ }
+
+ const { pages, ...restQuery } = query as InfiniteData<{
+ data: Array;
+ }>;
+
+ const newQuery = {
+ pages: pages.map(({ data, ...restPage }) => ({
+ data: data.map((question) => {
+ if (question.id === id) {
+ const { numVotes, ...restQuestion } = question;
+ return {
+ numVotes: numVotes + voteValueChange,
+ ...restQuestion,
+ };
+ }
+ return question;
+ }),
+ ...restPage,
+ })),
+ ...restQuery,
+ };
+
+ utils.queryClient.setQueryData(key, newQuery);
+ }
+
+ const prevQuestion = utils.queryClient.getQueryData([
+ 'questions.questions.getQuestionById',
+ {
+ id,
+ },
+ ]) as Question;
+
+ const newQuestion = {
+ ...prevQuestion,
+ numVotes: prevQuestion.numVotes + voteValueChange,
+ };
+
+ utils.queryClient.setQueryData(
+ ['questions.questions.getQuestionById', { id }],
+ newQuestion,
+ );
+ },
query: 'questions.questions.user.getVote',
setDownVoteKey: 'questions.questions.user.setDownVote',
setNoVoteKey: 'questions.questions.user.setNoVote',
@@ -63,8 +132,8 @@ export const useAnswerVote = (id: string) => {
return useVote(id, {
idKey: 'answerId',
invalidateKeys: [
- 'questions.answers.getAnswers',
'questions.answers.getAnswerById',
+ 'questions.answers.getAnswers',
],
query: 'questions.answers.user.getVote',
setDownVoteKey: 'questions.answers.user.setDownVote',
@@ -95,9 +164,17 @@ export const useAnswerCommentVote = (id: string) => {
});
};
+type InvalidateFunction = (
+ previousVote: Vote | null,
+ currentVote: Vote | null,
+) => Promise;
+
type VoteProps = {
idKey: string;
- invalidateKeys: Array;
+ invalidateKeys: Array;
+ onMutate?: InvalidateFunction;
+
+ // Invalidate: Partial>;
query: VoteQueryKey;
setDownVoteKey: MutationKey;
setNoVoteKey: MutationKey;
@@ -116,6 +193,7 @@ export const useVote = (
const {
idKey,
invalidateKeys,
+ onMutate,
query,
setDownVoteKey,
setNoVoteKey,
@@ -125,11 +203,16 @@ export const useVote = (
const onVoteUpdate = useCallback(() => {
// TODO: Optimise query invalidation
- utils.invalidateQueries([query, { [idKey]: id } as any]);
+ // utils.invalidateQueries([query, { [idKey]: id } as any]);
for (const invalidateKey of invalidateKeys) {
- utils.invalidateQueries([invalidateKey]);
+ utils.invalidateQueries(invalidateKey);
+ // If (invalidateFunction === null) {
+ // utils.invalidateQueries([invalidateKey as QueryKey]);
+ // } else {
+ // invalidateFunction(utils, previousVote, currentVote);
+ // }
}
- }, [id, idKey, utils, query, invalidateKeys]);
+ }, [utils, invalidateKeys]);
const { data } = trpc.useQuery([
query,
@@ -143,7 +226,7 @@ export const useVote = (
const { mutate: setUpVote } = trpc.useMutation(
setUpVoteKey,
{
- onError: (err, variables, context) => {
+ onError: (_error, _variables, context) => {
if (context !== undefined) {
utils.setQueryData([query], context.previousData);
}
@@ -154,6 +237,11 @@ export const useVote = (
[query, { [idKey]: id } as any],
);
+ const currentData = {
+ ...(vote as any),
+ vote: Vote.UPVOTE,
+ } as BackendVote;
+
utils.setQueryData(
[
query,
@@ -161,9 +249,11 @@ export const useVote = (
[idKey]: id,
} as any,
],
- vote as any,
+ currentData as any,
);
- return { currentData: vote, previousData };
+
+ await onMutate?.(previousData?.vote ?? null, currentData?.vote ?? null);
+ return { currentData, previousData };
},
onSettled: onVoteUpdate,
},
@@ -171,7 +261,7 @@ export const useVote = (
const { mutate: setDownVote } = trpc.useMutation(
setDownVoteKey,
{
- onError: (error, variables, context) => {
+ onError: (_error, _variables, context) => {
if (context !== undefined) {
utils.setQueryData([query], context.previousData);
}
@@ -182,6 +272,11 @@ export const useVote = (
[query, { [idKey]: id } as any],
);
+ const currentData = {
+ ...vote,
+ vote: Vote.DOWNVOTE,
+ } as BackendVote;
+
utils.setQueryData(
[
query,
@@ -189,9 +284,11 @@ export const useVote = (
[idKey]: id,
} as any,
],
- vote,
+ currentData as any,
);
- return { currentData: vote, previousData };
+
+ await onMutate?.(previousData?.vote ?? null, currentData?.vote ?? null);
+ return { currentData, previousData };
},
onSettled: onVoteUpdate,
},
@@ -200,23 +297,31 @@ export const useVote = (
const { mutate: setNoVote } = trpc.useMutation(
setNoVoteKey,
{
- onError: (err, variables, context) => {
+ onError: (_error, _variables, context) => {
if (context !== undefined) {
utils.setQueryData([query], context.previousData);
}
},
- onMutate: async (vote) => {
+ onMutate: async () => {
await utils.queryClient.cancelQueries([query, { [idKey]: id } as any]);
- utils.setQueryData(
+ const previousData = utils.queryClient.getQueryData(
+ [query, { [idKey]: id } as any],
+ );
+ const currentData: BackendVote | null = null;
+
+ utils.queryClient.setQueryData(
[
query,
{
[idKey]: id,
} as any,
],
- null as any,
+ currentData,
);
- return { currentData: null, previousData: vote };
+
+ await onMutate?.(previousData?.vote ?? null, null);
+
+ return { currentData, previousData };
},
onSettled: onVoteUpdate,
},