Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): add ability to edit question on questions page and single question page for admin #502

Merged
merged 26 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
04ad078
feat(app): add ability to edit questions on questions list to admin
grzegorzpokorski Jan 13, 2023
cd8cdaf
feat(app): add ability to edit question on question page to admin
grzegorzpokorski Jan 13, 2023
4a995d4
fix(app): refetch questions after edit one of them in user panel
grzegorzpokorski Jan 13, 2023
cd56f09
fix(app): fix text align in "CtaHedaerActiveLink"
grzegorzpokorski Jan 13, 2023
64b8f10
Remove unused import
grzegorzpokorski Jan 13, 2023
2f61628
Merge branch 'develop' into 479-issue
grzegorzpokorski Jan 15, 2023
68c0cbb
chore: add prettier config file to worspace config
grzegorzpokorski Jan 20, 2023
080d2b4
fix: update questions list after edit any of them in user dashboard
grzegorzpokorski Jan 20, 2023
eebca85
fix: add loading indicator to questions list in dashboards
grzegorzpokorski Jan 20, 2023
7b1b878
fix: disable ssr for QuestionsManagement on questions pages
grzegorzpokorski Jan 20, 2023
686b623
fix: small improvements in "EditAnswer" and "AnswerForm"
grzegorzpokorski Jan 20, 2023
34f3b48
fix: remove unused import
grzegorzpokorski Jan 20, 2023
f3eb9fe
fix: refresh ssr generated page after edit question
grzegorzpokorski Jan 20, 2023
db0594d
fix: remove unused import
grzegorzpokorski Jan 20, 2023
9b13a5e
fix: fix vercel deployment
grzegorzpokorski Jan 20, 2023
e4dd509
Merge branch 'fix-user-questions-dashboard' into 479-issue
grzegorzpokorski Jan 20, 2023
536a05f
fix: rename function name "customOnClose" => "additionalActionOnClose"
grzegorzpokorski Jan 20, 2023
f747452
feat: change loading indicator component
grzegorzpokorski Jan 20, 2023
906eb57
fix: improve "Loading" component styles
grzegorzpokorski Jan 20, 2023
d4e5a74
fix: improve loader a11y
grzegorzpokorski Jan 20, 2023
a92f98b
fix: improve styles of "Loading" component
grzegorzpokorski Jan 20, 2023
32f3641
fix: improve light theme of loading component
grzegorzpokorski Jan 20, 2023
2155e50
Update apps/app/src/components/Loading.tsx
grzegorzpokorski Jan 21, 2023
c14f3fa
Merge branch 'develop' into 479-issue
grzegorzpokorski Jan 23, 2023
e87aebb
implement "additionalActionOnClose" function in dashboard modals
grzegorzpokorski Jan 23, 2023
2d109c5
improve loading component and implement this changes
grzegorzpokorski Jan 23, 2023
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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
},
"prettier.configPath": "prettier.config.js"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Loading } from "../../../../../components/Loading";

export default function LoadingQuestions() {
return <Loading label="ładowanie pytań" />;
return <Loading label="ładowanie pytań" type="article" withTechnology={false} withFilters />;
}
13 changes: 10 additions & 3 deletions apps/app/src/components/AddQuestionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const AddQuestionModal = (props: ComponentProps<typeof BaseModal>) => {
onSuccess: () => {
setSelectData({});
setContent("");
modalData ? closeModal() : openModal("AddQuestionConfirmationModal");
modalData ? handleCloseModal() : openModal("AddQuestionConfirmationModal");
},
onError: () => setIsError(true),
};
Expand All @@ -78,8 +78,15 @@ export const AddQuestionModal = (props: ComponentProps<typeof BaseModal>) => {
}
};

const handleCloseModal = () => {
closeModal();
if (modalData?.additionalActionOnClose) {
modalData.additionalActionOnClose();
}
};

return (
<BaseModal {...props}>
<BaseModal {...props} onClose={handleCloseModal}>
<BaseModal.Title modalId={props.modalId}>
{modalData ? "Edytuj" : "Nowe"} pytanie
</BaseModal.Title>
Expand Down Expand Up @@ -135,7 +142,7 @@ export const AddQuestionModal = (props: ComponentProps<typeof BaseModal>) => {
<Button
type="button"
variant="branding"
onClick={closeModal}
onClick={handleCloseModal}
className="dark:focus:shadow-white"
>
Anuluj
Expand Down
3 changes: 2 additions & 1 deletion apps/app/src/components/AdminPanel/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Level } from "../../lib/level";
import { QuestionStatus } from "../../lib/question";
import { Technology } from "../../lib/technologies";
import { FilterableQuestionsList } from "../FilterableQuestionsList/FilterableQuestionsList";
import { Loading } from "../Loading";
import { AdminPanelQuestionsList } from "./AdminPanelQuestionsList";

type AdminPanelProps = Readonly<{
Expand Down Expand Up @@ -35,7 +36,7 @@ export const AdminPanel = ({ page, technology, levels, status }: AdminPanelProps
data={{ status, technology, levels }}
>
{isSuccess && data.data.data.length > 0 ? (
<Suspense>
<Suspense fallback={<Loading label="ładowanie pytań" type="article" />}>
<AdminPanelQuestionsList questions={data.data.data} refetchQuestions={refetchQuestions} />
</Suspense>
) : (
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/CtaHeader/CtaHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type CtaHeaderActiveLinkProps = Readonly<{
const CtaHeaderActiveLink = (props: CtaHeaderActiveLinkProps) => (
<ActiveLink
activeClassName="font-bold border-white"
className="flex h-12 grow items-center justify-center border-b-2 border-transparent px-7 transition-opacity hover:opacity-80"
className="flex h-12 grow items-center justify-center border-b-2 border-transparent px-7 text-center transition-opacity hover:opacity-80"
{...props}
/>
);
Expand Down
79 changes: 76 additions & 3 deletions apps/app/src/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,83 @@
import { twMerge } from "tailwind-merge";

export const Loading = ({ label, className }: { label: string; className?: string }) => {
type LoaderType = "spinner" | "article";

export const Loading = ({
label,
type = "spinner",
withTechnology = false,
withFilters = false,
className,
}: {
label: string;
withTechnology?: boolean;
type?: LoaderType;
withFilters?: boolean;
className?: string;
}) => {
if (type === "article") {
return (
<div role="status" aria-label={label}>
{withFilters && (
<div className="flex animate-pulse flex-wrap items-baseline justify-between gap-3 text-neutral-500 dark:text-neutral-400">
<span className="h-8 w-28 bg-neutral-100 dark:bg-neutral-700 md:gap-3"></span>
<div className="flex flex-wrap items-baseline gap-1.5 md:gap-3">
<span className="h-8 w-28 bg-neutral-100 dark:bg-neutral-700 md:gap-3"></span>
<span className="hidden h-8 w-52 bg-neutral-100 dark:bg-neutral-700 md:flex md:gap-3"></span>
</div>
</div>
)}
<ul className="flex w-full list-none flex-col gap-5">
{[...Array(10).keys()].map((i) => (
<li key={i}>
<article
className={twMerge(
`flex animate-pulse bg-white p-3 text-sm text-neutral-500 shadow-md dark:bg-white-dark dark:text-neutral-200 md:p-5`,
)}
style={{ animationDelay: `${i * 0.25}s` }}
>
<div className="mr-3 flex flex-col justify-start gap-1.5">
<span className="block h-8 w-20 rounded-md bg-neutral-100 dark:bg-neutral-700"></span>
</div>
<div className="question-content flex max-w-full grow flex-col gap-1.5 px-2">
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
<span className="block h-3.5 w-full bg-neutral-100 dark:bg-neutral-700"></span>
</div>
<div className="flex min-w-max flex-col items-center md:ml-4 md:items-end">
<div className="flex flex-col items-center">
{withTechnology && (
<div className="mb-3 flex flex-col items-center self-center">
<span className="mb-1 h-3.5 w-10 bg-neutral-100 dark:bg-neutral-700"></span>
<div className="h-8 w-8 rounded-full bg-neutral-100 dark:bg-neutral-700"></div>
</div>
)}
<span className="h-8 min-w-[64px] rounded-full bg-neutral-100 dark:bg-neutral-700 md:min-w-[80px]"></span>
</div>
<div className="mt-3">
<div className="block h-3 w-12 bg-neutral-100 dark:bg-neutral-700 md:w-20"></div>
</div>
</div>
</article>
</li>
))}
</ul>
</div>
);
}

return (
<div className={twMerge(`mt-8 flex items-center justify-center`, className)} aria-label={label}>
<div
className={twMerge(`mt-8 flex items-center justify-center`, className)}
aria-label={label}
role="status"
>
<div className="h-12 w-12 animate-loader border-4 border-primary dark:border-neutral-200">
<div className="w-full animate-loader-inner bg-primary dark:bg-neutral-200" />
<div className="w-full animate-loader-inner bg-primary dark:bg-neutral-100" />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export const AnswerForm = ({
const [isError, setIsError] = useState(false);

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

const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/QuestionAnswers/EditAnswer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const EditAnswer = ({
return (
<div className="mt-4">
<AnswerForm
title="Edytuj odpowiedź"
title="Zapisz zmiany"
initContent={content}
initSources={sources}
error={isError}
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/QuestionItem/QuestionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Technology } from "../../lib/technologies";
import { formatDate } from "../../utils/intl";
import { Box } from "../Box";
import { MarkdownContent } from "../MarkdownContent";
import { Level, QuestionLevel } from "./QuestionLevel";
import { Level } from "./QuestionLevel";

type QuestionItemProps = Readonly<{
id: number;
Expand Down
37 changes: 27 additions & 10 deletions apps/app/src/components/QuestionsList/QuestionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
"use client";

import dynamic from "next/dynamic";
import { useGetQuestionsVotes } from "../../hooks/useQuestionVoting";
import { Question, QuestionFilter } from "../../types";
import { AdminQuestion, QuestionFilter } from "../../types";
import { QuestionItem } from "../QuestionItem/QuestionItem";
import { QuestionLevel } from "../QuestionItem/QuestionLevel";
import { QuestionVoting } from "./QuestionVoting";

type QuestionsListProps = Readonly<{
questions: Question[];
questions: AdminQuestion[];
questionFilter: QuestionFilter;
}>;

const QuestionsManagement = dynamic(
() =>
import(/* webpackChunkName: "QuestionsManagement" */ "./QuestionsManagment").then(
(mod) => mod.QuestionsManagement,
),
{
ssr: false,
},
);

export const QuestionsList = ({ questions, questionFilter }: QuestionsListProps) => {
const { questionsVotes, refetch } = useGetQuestionsVotes(questionFilter);

Expand All @@ -20,8 +31,11 @@ export const QuestionsList = ({ questions, questionFilter }: QuestionsListProps)

return (
<ul className="space-y-10">
{questions.map(({ id, mdxContent, _levelId, _categoryId, acceptedAt }) => {
const questionVote = questionsVotes?.find((questionVote) => questionVote.id === id);
{questions.map((question) => {
const { id, mdxContent, _levelId, _categoryId, acceptedAt } = question;
const questionVote = questionsVotes?.find(
(questionVote) => questionVote.id === question.id,
);
const [votes, voted] = questionVote
? [questionVote.votesCount, questionVote.currentUserVotedOn]
: [0, false];
Expand All @@ -35,12 +49,15 @@ export const QuestionsList = ({ questions, questionFilter }: QuestionsListProps)
technology={_categoryId}
acceptedAt={acceptedAt}
leftSection={
<QuestionVoting
questionId={id}
votes={votes}
voted={voted}
onQuestionVote={onQuestionVote}
/>
<div className="flex flex-col gap-1.5">
<QuestionVoting
questionId={id}
votes={votes}
voted={voted}
onQuestionVote={onQuestionVote}
/>
<QuestionsManagement question={question} />
</div>
}
rightSection={<QuestionLevel level={_levelId} />}
/>
Expand Down
36 changes: 36 additions & 0 deletions apps/app/src/components/QuestionsList/QuestionsManagment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useRouter } from "next/navigation";
import { useCallback } from "react";
import { Button } from "../Button/Button";
import { PrivateElement } from "../PrivateElement";
import PencilIcon from "../../../public/icons/pencil.svg";
import { AdminQuestion } from "../../types";
import { useUIContext } from "../../providers/UIProvider";

export const QuestionsManagement = ({ question }: { question: AdminQuestion }) => {
const { openModal } = useUIContext();

const router = useRouter();
const onQuestionUpdate = useCallback(() => {
router.refresh();
}, [router]);

return (
<PrivateElement role="admin">
<div className="mr-3 flex flex-col justify-start gap-1.5">
<Button
variant="branding"
className="m-px flex w-24 min-w-0 items-center justify-center gap-2 p-0"
onClick={() =>
openModal("AddQuestionModal", {
...question,
additionalActionOnClose: onQuestionUpdate,
})
}
>
<PencilIcon className="fill-violet-700 dark:fill-neutral-200" />
Edytuj
</Button>
</div>
</PrivateElement>
);
};
39 changes: 26 additions & 13 deletions apps/app/src/components/SingleQuestion.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
"use client";

import { Question } from "../types";
import dynamic from "next/dynamic";
import { AdminQuestion } from "../types";
import { useGetQuestionVotesById } from "../hooks/useQuestionVoting";
import { QuestionItem } from "./QuestionItem/QuestionItem";
import { QuestionVoting } from "./QuestionsList/QuestionVoting";
import { QuestionTechnology } from "./QuestionItem/QuestionTechnology";
import { QuestionLevel } from "./QuestionItem/QuestionLevel";

const QuestionsManagement = dynamic(
() =>
import(
/* webpackChunkName: "QuestionsManagement" */ "../components/QuestionsList/QuestionsManagment"
).then((mod) => mod.QuestionsManagement),
{
ssr: false,
},
);

type SingleQuestionProps = Readonly<{
question: Question;
question: AdminQuestion;
}>;

export const SingleQuestion = ({
question: { id, mdxContent, _levelId, _categoryId, acceptedAt },
}: SingleQuestionProps) => {
export const SingleQuestion = ({ question }: SingleQuestionProps) => {
const { id, mdxContent, _levelId, _categoryId, acceptedAt } = question;
const { votes, voted, refetch } = useGetQuestionVotesById(id);

return (
Expand All @@ -24,14 +34,17 @@ export const SingleQuestion = ({
technology={_categoryId}
acceptedAt={acceptedAt}
leftSection={
<QuestionVoting
questionId={id}
votes={votes}
voted={voted}
onQuestionVote={() => {
void refetch();
}}
/>
<div className="flex flex-col gap-1.5">
<QuestionVoting
questionId={id}
votes={votes}
voted={voted}
onQuestionVote={() => {
void refetch();
}}
/>
<QuestionsManagement question={question} />
</div>
}
rightSection={
<div className="flex flex-col items-center">
Expand Down
15 changes: 13 additions & 2 deletions apps/app/src/components/UserQuestions/UserQuestionLeftSection.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { useEffect } from "react";
import { useUIContext } from "../../providers/UIProvider";
import { AdminQuestion } from "../../types";
import PencilIcon from "../../../public/icons/pencil.svg";
import { Button } from "../Button/Button";

type UserQuestionLeftSectionProps = Readonly<{
question: AdminQuestion;
refetchQuestions: () => void;
}>;

export const UserQuestionLeftSection = ({ question }: UserQuestionLeftSectionProps) => {
const { openModal } = useUIContext();
export const UserQuestionLeftSection = ({
question,
refetchQuestions,
}: UserQuestionLeftSectionProps) => {
const { openModal, openedModal } = useUIContext();

useEffect(() => {
if (!openedModal) {
refetchQuestions();
grzegorzpokorski marked this conversation as resolved.
Show resolved Hide resolved
}
}, [openedModal, refetchQuestions]);

return (
<div className="mr-3 flex flex-col justify-start gap-1.5">
Expand Down
Loading