Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}),
);

Expand Down
23 changes: 23 additions & 0 deletions apps/app/src/components/QuestionAnswers/AddAnswer.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="mt-6 bg-white p-5 dark:bg-white-dark">
<AnswerForm
title="Dodaj odpowiedź"
onSubmit={async ({ content, sources }) => {
await createQuestionAnswerMutation.mutateAsync({ id: questionId, content, sources });
}}
/>
</div>
);
};

This file was deleted.

48 changes: 26 additions & 22 deletions apps/app/src/components/QuestionAnswers/Answer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { formatDate } from "../../utils/intl";
import { Box } from "../Box";
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 (
Expand All @@ -33,28 +35,30 @@ export const Answer = ({ answer: { mdxContent, createdBy, createdAt, sources } }
</time>
</div>
</header>
<div className="-mx-2 mt-4">
<MarkdownContent source={mdxContent} />
</div>
{sources.length > 0 && (
<div className="mt-4 text-sm italic text-neutral-500 dark:text-neutral-400">
Źródła:
<ol className="list-decimal px-3 underline">
{sources.map((source, i) => (
<li key={i}>
<a
href={source}
target="_blank"
rel="noreferrer"
className="block w-full overflow-hidden text-ellipsis"
>
{source}
</a>
</li>
))}
</ol>
<EditAnswer answer={answer}>
<div className="-mx-2 mt-4">
<MarkdownContent source={mdxContent} />
</div>
)}
{sources.length > 0 && (
<div className="mt-4 text-sm italic text-neutral-500 dark:text-neutral-400">
Źródła:
<ol className="list-decimal px-3 underline">
{sources.map((source, i) => (
<li key={i}>
<a
href={source}
target="_blank"
rel="noreferrer"
className="block w-full overflow-hidden text-ellipsis"
>
{source}
</a>
</li>
))}
</ol>
</div>
)}
</EditAnswer>
</Box>
);
};
74 changes: 74 additions & 0 deletions apps/app/src/components/QuestionAnswers/AnswerForm/AnswerForm.tsx
Original file line number Diff line number Diff line change
@@ -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<void>;
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<string[]>(initSources || []);
const [isError, setIsError] = useState(false);

const disabled =
content.trim().length === 0 || !sources.every((source) => URL_REGEX.test(source));

const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

onSubmit({ content, sources })
.then(() => {
setContent("");
setSources([]);
router.refresh();
})
.catch(() => setIsError(true));
};

return (
<>
<h3 className="text-md font-bold dark:text-neutral-200">{title}</h3>
<form onSubmit={handleFormSubmit}>
<WysiwygEditor value={content} onChange={setContent} />
<AnswerSources sources={sources} onChange={setSources} />
<div className="flex flex-col gap-y-1">
<Button
type="submit"
variant="brandingInverse"
className="mt-6 w-full"
disabled={disabled}
>
{title}
</Button>
{children}
</div>
</form>
<Error visible={error || isError} className="mt-2" />
</>
);
};
76 changes: 76 additions & 0 deletions apps/app/src/components/QuestionAnswers/EditAnswer.tsx
Original file line number Diff line number Diff line change
@@ -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 { patchQuestionAnswerMutation, 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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

};

if (isEditMode) {
return (
<div className="mt-4">
<AnswerForm
title="Edytuj odpowiedź"
initContent={content}
initSources={sources}
error={isError}
onSubmit={async ({ content, sources }) => {
await patchQuestionAnswerMutation.mutateAsync({ id, content, sources });
setIsEditMode(false);
}}
>
<Button type="button" variant="brandingInverse" onClick={handleDeleteButtonClick}>
Usuń odpowiedź
</Button>
<Button type="button" variant="branding" onClick={() => setIsEditMode(false)}>
Anuluj
</Button>
</AnswerForm>
</div>
);
}

return (
<>
{children}
{userData?._user.id === createdBy.id || userData?._user._roleId === "admin" ? (
<Button variant="branding" className="mt-2 w-full" onClick={() => setIsEditMode(true)}>
Edytuj odpowiedź
</Button>
) : null}
</>
);
};
4 changes: 2 additions & 2 deletions apps/app/src/components/QuestionAnswers/QuestionAnswers.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,7 +21,7 @@ export const QuestionAnswers = ({ questionId, answers }: QuestionAnswersProps) =
)}
</div>
<AddAnswerTip>
<AddAnswerForm questionId={questionId} />
<AddAnswer questionId={questionId} />
</AddAnswerTip>
</div>
);
6 changes: 6 additions & 0 deletions apps/app/src/hooks/useQuestionMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import {
createQuestion,
createQuestionAnswer,
deleteQuestion,
deleteQuestionAnswer,
patchQuestion,
patchQuestionAnswer,
} from "../services/questions.service";

export const useQuestionMutation = () => {
const createQuestionMutation = useMutation(createQuestion);
const deleteQuestionMutation = useMutation(deleteQuestion);
const patchQuestionMutation = useMutation(patchQuestion);
const createQuestionAnswerMutation = useMutation(createQuestionAnswer);
const patchQuestionAnswerMutation = useMutation(patchQuestionAnswer);
const deleteQuestionAnswerMutation = useMutation(deleteQuestionAnswer);

return {
createQuestionMutation,
deleteQuestionMutation,
patchQuestionMutation,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu też powinniśmy zmienić nazwę na update zamiast patch

createQuestionAnswerMutation,
patchQuestionAnswerMutation,
deleteQuestionAnswerMutation,
};
};
4 changes: 4 additions & 0 deletions apps/app/src/services/questions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 patchQuestionAnswer = fetcher.path("/answers/{id}").method("patch").create();

export const deleteQuestionAnswer = fetcher.path("/answers/{id}").method("delete").create();
8 changes: 3 additions & 5 deletions apps/app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ export type QuestionFilter = ExcludeUndefined<
ExcludeUndefined<paths["/questions"]["get"]["parameters"]>["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<T extends string> = {
readonly [K in T]: string;
Expand Down