From 6c4e6b111fa3f5b97451bc95cea869fda39a5a05 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Fri, 6 Jan 2023 19:43:10 +0100 Subject: [PATCH 1/2] feat(app): add answer editor to question answer --- .../questions/p/[questionId]/page.tsx | 6 +- .../components/QuestionAnswers/AddAnswer.tsx | 23 ++++++ .../AddAnswerForm/AddAnswerForm.tsx | 61 --------------- .../src/components/QuestionAnswers/Answer.tsx | 48 ++++++------ .../QuestionAnswers/AnswerForm/AnswerForm.tsx | 74 ++++++++++++++++++ .../AnswerSources.tsx | 0 .../components/QuestionAnswers/EditAnswer.tsx | 76 +++++++++++++++++++ .../QuestionAnswers/QuestionAnswers.tsx | 4 +- apps/app/src/hooks/useQuestionMutation.ts | 6 ++ apps/app/src/services/questions.service.ts | 4 + apps/app/src/types.ts | 8 +- 11 files changed, 217 insertions(+), 93 deletions(-) create mode 100644 apps/app/src/components/QuestionAnswers/AddAnswer.tsx delete mode 100644 apps/app/src/components/QuestionAnswers/AddAnswerForm/AddAnswerForm.tsx create mode 100644 apps/app/src/components/QuestionAnswers/AnswerForm/AnswerForm.tsx rename apps/app/src/components/QuestionAnswers/{AddAnswerForm => AnswerForm}/AnswerSources.tsx (100%) create mode 100644 apps/app/src/components/QuestionAnswers/EditAnswer.tsx diff --git a/apps/app/src/app/(main-layout)/questions/p/[questionId]/page.tsx b/apps/app/src/app/(main-layout)/questions/p/[questionId]/page.tsx index 80d84a63..beae24d2 100644 --- a/apps/app/src/app/(main-layout)/questions/p/[questionId]/page.tsx +++ b/apps/app/src/app/(main-layout)/questions/p/[questionId]/page.tsx @@ -29,9 +29,9 @@ export default async function SingleQuestionPage({ params }: { params: Params<"q } const answers = await Promise.all( - answersData.data.data.map(async ({ content, ...rest }) => { - const mdxContent = await serializeSource(content); - return { mdxContent, ...rest }; + answersData.data.data.map(async (answer) => { + const mdxContent = await serializeSource(answer.content); + return { mdxContent, ...answer }; }), ); diff --git a/apps/app/src/components/QuestionAnswers/AddAnswer.tsx b/apps/app/src/components/QuestionAnswers/AddAnswer.tsx new file mode 100644 index 00000000..eabafd05 --- /dev/null +++ b/apps/app/src/components/QuestionAnswers/AddAnswer.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { useQuestionMutation } from "../../hooks/useQuestionMutation"; +import { AnswerForm } from "./AnswerForm/AnswerForm"; + +type AddAnswerProps = Readonly<{ + questionId: number; +}>; + +export const AddAnswer = ({ questionId }: AddAnswerProps) => { + const { createQuestionAnswerMutation } = useQuestionMutation(); + + return ( +
+ { + await createQuestionAnswerMutation.mutateAsync({ id: questionId, content, sources }); + }} + /> +
+ ); +}; diff --git a/apps/app/src/components/QuestionAnswers/AddAnswerForm/AddAnswerForm.tsx b/apps/app/src/components/QuestionAnswers/AddAnswerForm/AddAnswerForm.tsx deleted file mode 100644 index 32f82971..00000000 --- a/apps/app/src/components/QuestionAnswers/AddAnswerForm/AddAnswerForm.tsx +++ /dev/null @@ -1,61 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { FormEvent, useState } from "react"; -import { useQuestionMutation } from "../../../hooks/useQuestionMutation"; -import { Button } from "../../Button/Button"; -import { WysiwygEditor } from "../../WysiwygEditor/WysiwygEditor"; -import { URL_REGEX } from "../../../lib/constants"; -import { Error } from "../../Error"; -import { AnswerSources } from "./AnswerSources"; - -type AddAnswerFormProps = Readonly<{ - questionId: number; -}>; - -export const AddAnswerForm = ({ questionId }: AddAnswerFormProps) => { - const router = useRouter(); - - const [content, setContent] = useState(""); - const [sources, setSources] = useState([]); - const [isError, setIsError] = useState(false); - - const { createQuestionAnswerMutation } = useQuestionMutation(); - - const disabled = - content.trim().length === 0 || !sources.every((source) => URL_REGEX.test(source)); - - const handleFormSubmit = (event: FormEvent) => { - event.preventDefault(); - - createQuestionAnswerMutation.mutate( - { - id: questionId, - sources, - content, - }, - { - onSuccess: () => { - router.refresh(); - setContent(""); - setSources([]); - }, - onError: () => setIsError(true), - }, - ); - }; - - return ( -
-

Dodaj odpowiedź

-
- - - - - -
- ); -}; diff --git a/apps/app/src/components/QuestionAnswers/Answer.tsx b/apps/app/src/components/QuestionAnswers/Answer.tsx index 862d49eb..6319fa7f 100644 --- a/apps/app/src/components/QuestionAnswers/Answer.tsx +++ b/apps/app/src/components/QuestionAnswers/Answer.tsx @@ -2,12 +2,14 @@ import { QuestionAnswer } from "../../types"; import { formatDate } from "../../utils/intl"; import { GitHubAvatar } from "../GitHubAvatar"; import { MarkdownContent } from "../MarkdownContent"; +import { EditAnswer } from "./EditAnswer"; type AnswerProps = Readonly<{ answer: QuestionAnswer; }>; -export const Answer = ({ answer: { mdxContent, createdBy, createdAt, sources } }: AnswerProps) => { +export const Answer = ({ answer }: AnswerProps) => { + const { mdxContent, createdBy, createdAt, sources } = answer; const creationDate = new Date(createdAt); return ( @@ -32,28 +34,30 @@ export const Answer = ({ answer: { mdxContent, createdBy, createdAt, sources } } -
- -
- {sources.length > 0 && ( -
- Źródła: -
    - {sources.map((source, i) => ( -
  1. - - {source} - -
  2. - ))} -
+ +
+
- )} + {sources.length > 0 && ( +
+ Źródła: +
    + {sources.map((source, i) => ( +
  1. + + {source} + +
  2. + ))} +
+
+ )} +
); }; diff --git a/apps/app/src/components/QuestionAnswers/AnswerForm/AnswerForm.tsx b/apps/app/src/components/QuestionAnswers/AnswerForm/AnswerForm.tsx new file mode 100644 index 00000000..f46d6ef7 --- /dev/null +++ b/apps/app/src/components/QuestionAnswers/AnswerForm/AnswerForm.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { FormEvent, ReactNode, useState } from "react"; +import { Button } from "../../Button/Button"; +import { WysiwygEditor } from "../../WysiwygEditor/WysiwygEditor"; +import { URL_REGEX } from "../../../lib/constants"; +import { Error } from "../../Error"; +import { AnswerSources } from "./AnswerSources"; + +interface SubmitData { + readonly content: string; + readonly sources: string[]; +} + +type AnswerFormProps = Readonly<{ + title: string; + onSubmit: (data: SubmitData) => Promise; + initContent?: string; + initSources?: string[]; + error?: boolean; + children?: ReactNode; +}>; + +export const AnswerForm = ({ + title, + onSubmit, + initContent, + initSources, + error, + children, +}: AnswerFormProps) => { + const router = useRouter(); + const [content, setContent] = useState(initContent || ""); + const [sources, setSources] = useState(initSources || []); + const [isError, setIsError] = useState(false); + + const disabled = + content.trim().length === 0 || !sources.every((source) => URL_REGEX.test(source)); + + const handleFormSubmit = (event: FormEvent) => { + event.preventDefault(); + + onSubmit({ content, sources }) + .then(() => { + setContent(""); + setSources([]); + router.refresh(); + }) + .catch(() => setIsError(true)); + }; + + return ( + <> +

{title}

+
+ + +
+ + {children} +
+ + + + ); +}; diff --git a/apps/app/src/components/QuestionAnswers/AddAnswerForm/AnswerSources.tsx b/apps/app/src/components/QuestionAnswers/AnswerForm/AnswerSources.tsx similarity index 100% rename from apps/app/src/components/QuestionAnswers/AddAnswerForm/AnswerSources.tsx rename to apps/app/src/components/QuestionAnswers/AnswerForm/AnswerSources.tsx diff --git a/apps/app/src/components/QuestionAnswers/EditAnswer.tsx b/apps/app/src/components/QuestionAnswers/EditAnswer.tsx new file mode 100644 index 00000000..cab932d6 --- /dev/null +++ b/apps/app/src/components/QuestionAnswers/EditAnswer.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { ReactNode, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "../Button/Button"; +import { QuestionAnswer } from "../../types"; +import { useQuestionMutation } from "../../hooks/useQuestionMutation"; +import { useUser } from "../../hooks/useUser"; +import { AnswerForm } from "./AnswerForm/AnswerForm"; + +type EditAnswerProps = Readonly<{ + answer: QuestionAnswer; + children: ReactNode; +}>; + +export const EditAnswer = ({ + answer: { id, content, sources, createdBy }, + children, +}: EditAnswerProps) => { + const router = useRouter(); + + const [isEditMode, setIsEditMode] = useState(false); + const [isError, setIsError] = useState(false); + + const { userData } = useUser(); + const { updateQuestionAnswerMutation, deleteQuestionAnswerMutation } = useQuestionMutation(); + + const handleDeleteButtonClick = () => { + const confirmed = confirm("Czy na pewno chcesz usunąć tę odpowiedź?"); + + if (!confirmed) return; + + deleteQuestionAnswerMutation.mutate( + { id }, + { + onError: () => setIsError(true), + }, + ); + router.refresh(); + }; + + if (isEditMode) { + return ( +
+ { + await updateQuestionAnswerMutation.mutateAsync({ id, content, sources }); + setIsEditMode(false); + }} + > + + + +
+ ); + } + + return ( + <> + {children} + {userData?._user.id === createdBy.id || userData?._user._roleId === "admin" ? ( + + ) : null} + + ); +}; diff --git a/apps/app/src/components/QuestionAnswers/QuestionAnswers.tsx b/apps/app/src/components/QuestionAnswers/QuestionAnswers.tsx index 43140c2c..a68f2dc7 100644 --- a/apps/app/src/components/QuestionAnswers/QuestionAnswers.tsx +++ b/apps/app/src/components/QuestionAnswers/QuestionAnswers.tsx @@ -1,7 +1,7 @@ import type { QuestionAnswer } from "../../types"; -import { AddAnswerForm } from "./AddAnswerForm/AddAnswerForm"; import { AddAnswerTip } from "./AddAnswerTip"; import { Answer } from "./Answer"; +import { AddAnswer } from "./AddAnswer"; type QuestionAnswersProps = Readonly<{ questionId: number; @@ -21,7 +21,7 @@ export const QuestionAnswers = ({ questionId, answers }: QuestionAnswersProps) = )} - + ); diff --git a/apps/app/src/hooks/useQuestionMutation.ts b/apps/app/src/hooks/useQuestionMutation.ts index 5f0aefee..5c002580 100644 --- a/apps/app/src/hooks/useQuestionMutation.ts +++ b/apps/app/src/hooks/useQuestionMutation.ts @@ -3,7 +3,9 @@ import { createQuestion, createQuestionAnswer, deleteQuestion, + deleteQuestionAnswer, patchQuestion, + updateQuestionAnswer, } from "../services/questions.service"; export const useQuestionMutation = () => { @@ -11,11 +13,15 @@ export const useQuestionMutation = () => { const deleteQuestionMutation = useMutation(deleteQuestion); const patchQuestionMutation = useMutation(patchQuestion); const createQuestionAnswerMutation = useMutation(createQuestionAnswer); + const updateQuestionAnswerMutation = useMutation(updateQuestionAnswer); + const deleteQuestionAnswerMutation = useMutation(deleteQuestionAnswer); return { createQuestionMutation, deleteQuestionMutation, patchQuestionMutation, createQuestionAnswerMutation, + updateQuestionAnswerMutation, + deleteQuestionAnswerMutation, }; }; diff --git a/apps/app/src/services/questions.service.ts b/apps/app/src/services/questions.service.ts index 8806240b..b8ff1eac 100644 --- a/apps/app/src/services/questions.service.ts +++ b/apps/app/src/services/questions.service.ts @@ -21,3 +21,7 @@ export const patchQuestion = fetcher.path("/questions/{id}").method("patch").cre export const getQuestionAnswers = fetcher.path("/questions/{id}/answers").method("get").create(); export const createQuestionAnswer = fetcher.path("/questions/{id}/answers").method("post").create(); + +export const updateQuestionAnswer = fetcher.path("/answers/{id}").method("patch").create(); + +export const deleteQuestionAnswer = fetcher.path("/answers/{id}").method("delete").create(); diff --git a/apps/app/src/types.ts b/apps/app/src/types.ts index 42c58908..c8de8639 100644 --- a/apps/app/src/types.ts +++ b/apps/app/src/types.ts @@ -23,11 +23,9 @@ export type QuestionFilter = ExcludeUndefined< ExcludeUndefined["query"] >; -export type QuestionAnswer = Omit< - paths["/questions/{id}/answers"]["get"]["responses"]["200"]["content"]["application/json"]["data"][0], - "content" -> & - MdxContent; +export type QuestionAnswer = + paths["/questions/{id}/answers"]["get"]["responses"]["200"]["content"]["application/json"]["data"][0] & + MdxContent; export type Params = { readonly [K in T]: string; From c0c87eb8cf0c7d321626b5f5c8cd313d3f4e3866 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 8 Jan 2023 16:19:42 +0100 Subject: [PATCH 2/2] refactor(app): change mutation name --- apps/app/src/components/QuestionAnswers/EditAnswer.tsx | 4 ++-- apps/app/src/hooks/useQuestionMutation.ts | 6 +++--- apps/app/src/services/questions.service.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/app/src/components/QuestionAnswers/EditAnswer.tsx b/apps/app/src/components/QuestionAnswers/EditAnswer.tsx index cab932d6..a1834d28 100644 --- a/apps/app/src/components/QuestionAnswers/EditAnswer.tsx +++ b/apps/app/src/components/QuestionAnswers/EditAnswer.tsx @@ -23,7 +23,7 @@ export const EditAnswer = ({ const [isError, setIsError] = useState(false); const { userData } = useUser(); - const { updateQuestionAnswerMutation, deleteQuestionAnswerMutation } = useQuestionMutation(); + const { patchQuestionAnswerMutation, deleteQuestionAnswerMutation } = useQuestionMutation(); const handleDeleteButtonClick = () => { const confirmed = confirm("Czy na pewno chcesz usunąć tę odpowiedź?"); @@ -48,7 +48,7 @@ export const EditAnswer = ({ initSources={sources} error={isError} onSubmit={async ({ content, sources }) => { - await updateQuestionAnswerMutation.mutateAsync({ id, content, sources }); + await patchQuestionAnswerMutation.mutateAsync({ id, content, sources }); setIsEditMode(false); }} > diff --git a/apps/app/src/hooks/useQuestionMutation.ts b/apps/app/src/hooks/useQuestionMutation.ts index 5c002580..a23dd6b3 100644 --- a/apps/app/src/hooks/useQuestionMutation.ts +++ b/apps/app/src/hooks/useQuestionMutation.ts @@ -5,7 +5,7 @@ import { deleteQuestion, deleteQuestionAnswer, patchQuestion, - updateQuestionAnswer, + patchQuestionAnswer, } from "../services/questions.service"; export const useQuestionMutation = () => { @@ -13,7 +13,7 @@ export const useQuestionMutation = () => { const deleteQuestionMutation = useMutation(deleteQuestion); const patchQuestionMutation = useMutation(patchQuestion); const createQuestionAnswerMutation = useMutation(createQuestionAnswer); - const updateQuestionAnswerMutation = useMutation(updateQuestionAnswer); + const patchQuestionAnswerMutation = useMutation(patchQuestionAnswer); const deleteQuestionAnswerMutation = useMutation(deleteQuestionAnswer); return { @@ -21,7 +21,7 @@ export const useQuestionMutation = () => { deleteQuestionMutation, patchQuestionMutation, createQuestionAnswerMutation, - updateQuestionAnswerMutation, + patchQuestionAnswerMutation, deleteQuestionAnswerMutation, }; }; diff --git a/apps/app/src/services/questions.service.ts b/apps/app/src/services/questions.service.ts index b8ff1eac..41ae9a53 100644 --- a/apps/app/src/services/questions.service.ts +++ b/apps/app/src/services/questions.service.ts @@ -22,6 +22,6 @@ export const getQuestionAnswers = fetcher.path("/questions/{id}/answers").method export const createQuestionAnswer = fetcher.path("/questions/{id}/answers").method("post").create(); -export const updateQuestionAnswer = fetcher.path("/answers/{id}").method("patch").create(); +export const patchQuestionAnswer = fetcher.path("/answers/{id}").method("patch").create(); export const deleteQuestionAnswer = fetcher.path("/answers/{id}").method("delete").create();