+
+
+
+
+
{
createQuestion({
@@ -250,6 +273,9 @@ export default function QuestionsHomePage() {
},
]}
sortValue="most-recent"
+ onFilterOptionsToggle={() => {
+ setFilterDrawerOpen(!filterDrawerOpen);
+ }}
onSortChange={(value) => {
// eslint-disable-next-line no-console
console.log(value);
@@ -257,24 +283,49 @@ export default function QuestionsHomePage() {
/>
{(questions ?? []).map((question) => (
))}
+ {questions?.length === 0 && (
+
+
+
Nothing found. Try changing your search filters.
+
+ )}
+
+
{
+ setFilterDrawerOpen(false);
+ }}>
+ {filterSidebar}
+
);
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 51d17d34..f75195c7 100644
--- a/apps/portal/src/server/router/questions-answer-comment-router.ts
+++ b/apps/portal/src/server/router/questions-answer-comment-router.ts
@@ -17,6 +17,7 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
include: {
user: {
select: {
+ image: true,
name: true,
},
},
@@ -54,6 +55,7 @@ export const questionsAnswerCommentRouter = createProtectedRouter()
numVotes: votes,
updatedAt: data.updatedAt,
user: data.user?.name ?? '',
+ userImage: data.user?.image ?? '',
};
return answerComment;
});
@@ -182,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.',
@@ -213,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 21095bf7..7dadbeb4 100644
--- a/apps/portal/src/server/router/questions-answer-router.ts
+++ b/apps/portal/src/server/router/questions-answer-router.ts
@@ -21,6 +21,7 @@ export const questionsAnswerRouter = createProtectedRouter()
},
user: {
select: {
+ image: true,
name: true,
},
},
@@ -58,6 +59,7 @@ export const questionsAnswerRouter = createProtectedRouter()
numComments: data._count.comments,
numVotes: votes,
user: data.user?.name ?? '',
+ userImage: data.user?.image ?? '',
};
return answer;
});
@@ -77,6 +79,7 @@ export const questionsAnswerRouter = createProtectedRouter()
},
user: {
select: {
+ image: true,
name: true,
},
},
@@ -116,6 +119,7 @@ export const questionsAnswerRouter = createProtectedRouter()
numComments: answerData._count.comments,
numVotes: votes,
user: answerData.user?.name ?? '',
+ userImage: answerData.user?.image ?? '',
};
return answer;
},
@@ -241,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.',
@@ -271,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 82345f06..f3985f31 100644
--- a/apps/portal/src/server/router/questions-question-comment-router.ts
+++ b/apps/portal/src/server/router/questions-question-comment-router.ts
@@ -17,6 +17,7 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
include: {
user: {
select: {
+ image: true,
name: true,
},
},
@@ -53,6 +54,7 @@ export const questionsQuestionCommentRouter = createProtectedRouter()
id: data.id,
numVotes: votes,
user: data.user?.name ?? '',
+ userImage: data.user?.image ?? '',
};
return questionComment;
});
@@ -181,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.',
@@ -212,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/types/questions.d.ts b/apps/portal/src/types/questions.d.ts
index 3797bf38..41286249 100644
--- a/apps/portal/src/types/questions.d.ts
+++ b/apps/portal/src/types/questions.d.ts
@@ -21,6 +21,7 @@ export type AnswerComment = {
numVotes: number;
updatedAt: Date;
user: string;
+ userImage: string;
};
export type Answer = {
@@ -30,6 +31,7 @@ export type Answer = {
numComments: number;
numVotes: number;
user: string;
+ userImage: string;
};
export type QuestionComment = {
@@ -38,4 +40,5 @@ export type QuestionComment = {
id: string;
numVotes: number;
user: string;
+ userImage: string;
};
diff --git a/apps/portal/src/utils/questions/useSearchFilter.ts b/apps/portal/src/utils/questions/useSearchFilter.ts
index 1a7bd199..0b916261 100644
--- a/apps/portal/src/utils/questions/useSearchFilter.ts
+++ b/apps/portal/src/utils/questions/useSearchFilter.ts
@@ -27,13 +27,6 @@ export const useSearchFilter =
(
if (localStorageValue !== null) {
const loadedFilters = JSON.parse(localStorageValue);
setFilters(loadedFilters);
- router.replace({
- pathname: router.pathname,
- query: {
- ...router.query,
- [name]: loadedFilters,
- },
- });
}
}
setIsInitialized(true);
@@ -44,15 +37,8 @@ export const useSearchFilter = (
(newFilters: Array) => {
setFilters(newFilters);
localStorage.setItem(name, JSON.stringify(newFilters));
- router.replace({
- pathname: router.pathname,
- query: {
- ...router.query,
- [name]: newFilters,
- },
- });
},
- [name, router],
+ [name],
);
return [filters, setFiltersCallback, isInitialized] as const;
@@ -73,9 +59,7 @@ export const useSearchFilterSingle = (
return [
filters[0],
- (value: Value) => {
- setFilters([value]);
- },
+ (value: Value) => setFilters([value]),
isInitialized,
] as const;
};
diff --git a/apps/portal/src/utils/questions/useVote.ts b/apps/portal/src/utils/questions/useVote.ts
new file mode 100644
index 00000000..e71a6a62
--- /dev/null
+++ b/apps/portal/src/utils/questions/useVote.ts
@@ -0,0 +1,175 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useCallback } from 'react';
+import type { Vote } from '@prisma/client';
+
+import { trpc } from '../trpc';
+
+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 = () => {
+ // 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 };
+};
+
+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(() => {
+ // TODO: Optimise query invalidation
+ utils.invalidateQueries([query, { [idKey]: id } as any]);
+ utils.invalidateQueries(['questions.questions.getQuestionsByFilter']);
+ utils.invalidateQueries(['questions.questions.getQuestionById']);
+ utils.invalidateQueries(['questions.answers.getAnswers']);
+ utils.invalidateQueries(['questions.answers.getAnswerById']);
+ utils.invalidateQueries([
+ 'questions.questions.comments.getQuestionComments',
+ ]);
+ utils.invalidateQueries(['questions.answers.comments.getAnswerComments']);
+ }, [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 { handleDownvote, handleUpvote, vote: backendVote ?? null };
+};