From e8164f8dc8840ad58ea0b50198bcbcaa94d1582c Mon Sep 17 00:00:00 2001 From: Marco Escaleira Date: Mon, 26 Feb 2024 00:37:31 +0000 Subject: [PATCH] feat: quiz create form now completed --- .../DifficultyChip/DifficultyChip.tsx | 6 +- .../QuestionTypeChip/QuestionTypeChip.tsx | 16 ++++ src/components/Quiz/CreateQuizDialog.tsx | 16 +++- src/components/Quiz/OptionsFields.tsx | 57 +++++++++++++ src/components/Quiz/QuestionsFields.tsx | 81 +++++++++++++++++++ src/components/Quiz/QuizForm.tsx | 51 +++++++----- src/components/Quiz/quizFormSchema.ts | 38 +++++++++ src/pages/Game/CountryQuizList.tsx | 6 +- 8 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 src/components/QuestionTypeChip/QuestionTypeChip.tsx create mode 100644 src/components/Quiz/OptionsFields.tsx create mode 100644 src/components/Quiz/QuestionsFields.tsx create mode 100644 src/components/Quiz/quizFormSchema.ts diff --git a/src/components/DifficultyChip/DifficultyChip.tsx b/src/components/DifficultyChip/DifficultyChip.tsx index 3d3c777..d9ef7f9 100644 --- a/src/components/DifficultyChip/DifficultyChip.tsx +++ b/src/components/DifficultyChip/DifficultyChip.tsx @@ -1,13 +1,15 @@ import { Chip } from "@material-tailwind/react"; +import { size } from "@material-tailwind/react/types/components/chip"; import { colors } from "@material-tailwind/react/types/generic"; import { Difficulty } from "@generated/graphql.ts"; interface DifficultyChipProps { // Difficulty of the quiz, if not provided then its Unknown difficulty?: Difficulty | null; + size?: size; } -export function DifficultyChip({ difficulty }: DifficultyChipProps) { +export function DifficultyChip({ difficulty, size = "md" }: DifficultyChipProps) { const color = { [Difficulty.Easy]: "green", [Difficulty.Medium]: "yellow", @@ -15,5 +17,5 @@ export function DifficultyChip({ difficulty }: DifficultyChipProps) { [Difficulty.Unknown]: "gray", }[difficulty || Difficulty.Unknown]; - return ; + return ; } diff --git a/src/components/QuestionTypeChip/QuestionTypeChip.tsx b/src/components/QuestionTypeChip/QuestionTypeChip.tsx new file mode 100644 index 0000000..b744280 --- /dev/null +++ b/src/components/QuestionTypeChip/QuestionTypeChip.tsx @@ -0,0 +1,16 @@ +import { Chip } from "@material-tailwind/react"; +import { colors } from "@material-tailwind/react/types/generic"; +import { QuestionType } from "@generated/graphql.ts"; + +interface QuestionTypeChipProps { + questionType: QuestionType; +} + +export function QuestionTypeChip({ questionType }: QuestionTypeChipProps) { + const color = { + [QuestionType.Single]: "green", + [QuestionType.Multi]: "blue", + }[questionType || QuestionType.Single]; + + return ; +} diff --git a/src/components/Quiz/CreateQuizDialog.tsx b/src/components/Quiz/CreateQuizDialog.tsx index d241391..541f6b4 100644 --- a/src/components/Quiz/CreateQuizDialog.tsx +++ b/src/components/Quiz/CreateQuizDialog.tsx @@ -5,9 +5,11 @@ import { Roles } from "@generated/graphql.ts"; import { useUserStore } from "@state/userStore.ts"; import { QuizForm } from "./QuizForm.tsx"; -interface CreateQuizDialogProps {} +interface CreateQuizDialogProps { + simpleButton?: boolean; +} -export function CreateQuizDialog({}: CreateQuizDialogProps) { +export function CreateQuizDialog({ simpleButton }: CreateQuizDialogProps) { const { user: { role }, } = useUserStore(); @@ -19,7 +21,13 @@ export function CreateQuizDialog({}: CreateQuizDialogProps) { return ( <> - @@ -30,7 +38,7 @@ export function CreateQuizDialog({}: CreateQuizDialogProps) { - + diff --git a/src/components/Quiz/OptionsFields.tsx b/src/components/Quiz/OptionsFields.tsx new file mode 100644 index 0000000..b76eec1 --- /dev/null +++ b/src/components/Quiz/OptionsFields.tsx @@ -0,0 +1,57 @@ +import { Checkbox, IconButton, Input, Typography } from "@material-tailwind/react"; +import { Plus, X } from "lucide-react"; +import { useFieldArray, useFormContext } from "react-hook-form"; +import { z } from "zod"; +import { quizFormSchema } from "./quizFormSchema"; + +interface OptionsFieldsProps { + questionIndex: number; +} + +export function OptionsFields({ questionIndex }: OptionsFieldsProps) { + const { + control, + register, + formState: { errors }, + } = useFormContext>(); + const { fields, append, remove } = useFieldArray({ + control, + name: `questions.${questionIndex}.options`, + }); + + return ( +
+
+ Options: {fields.length} + append({ text: "", correct: false })} color="green" size="sm"> + + +
+ + {fields.map((field, optionIndex) => ( +
+
+ Option #{optionIndex + 1} + remove(optionIndex)} variant="text" size="sm"> + + +
+
+ + +
+
+ ))} +
+ ); +} diff --git a/src/components/Quiz/QuestionsFields.tsx b/src/components/Quiz/QuestionsFields.tsx new file mode 100644 index 0000000..98f605b --- /dev/null +++ b/src/components/Quiz/QuestionsFields.tsx @@ -0,0 +1,81 @@ +import { cloneElement } from "react"; +import { IconButton, Input, Option, Select, Typography } from "@material-tailwind/react"; +import { Plus, X } from "lucide-react"; +import { useFieldArray, useFormContext } from "react-hook-form"; +import { z } from "zod"; +import { QuestionTypeChip } from "@components/QuestionTypeChip/QuestionTypeChip.tsx"; +import { QuestionType } from "@generated/graphql.ts"; +import { OptionsFields } from "./OptionsFields"; +import { quizFormSchema } from "./quizFormSchema"; + +export function QuestionFields() { + const { + control, + register, + getValues, + setValue, + formState: { errors }, + } = useFormContext>(); + const { fields, append, remove } = useFieldArray({ + control, + name: "questions", + }); + + return ( + <> +
+ Questions: {fields.length} + append({ question: "", type: QuestionType.Single, options: [{ text: "", correct: false }] })} + color="green" + size="sm" + > + + +
+ + {fields.map((field, questionIndex) => ( +
+
+ Question #{questionIndex + 1} + remove(questionIndex)} variant="text" size="sm"> + + +
+ + + + + + +
+
+ ))} + + ); +} diff --git a/src/components/Quiz/QuizForm.tsx b/src/components/Quiz/QuizForm.tsx index 3f31878..85f02a2 100644 --- a/src/components/Quiz/QuizForm.tsx +++ b/src/components/Quiz/QuizForm.tsx @@ -8,20 +8,12 @@ import { toast } from "react-toastify"; import { useCountries } from "use-react-countries"; import { z } from "zod"; import { DifficultyChip } from "@components/DifficultyChip/DifficultyChip.tsx"; -import { Difficulty, Roles } from "@generated/graphql.ts"; +import { QuestionFields } from "@components/Quiz/QuestionsFields.tsx"; +import { quizFormSchema } from "@components/Quiz/quizFormSchema.ts"; +import { Difficulty, QuestionType, Roles } from "@generated/graphql.ts"; import { useUserStore } from "@state/userStore.ts"; import { CREATE_QUIZ } from "@utils/queries/CreateQuiz.ts"; -const formSchema = z.object({ - title: z.string().min(5, { message: "Enter a title." }), - description: z.string().min(50, { message: "Enter a description." }), - country: z.string().min(1, { message: "Enter a country." }), - image: z.union([z.literal(""), z.string().trim().url()]), - // questions: z.array() - difficulty: z.nativeEnum(Difficulty), - timeLimit: z.number().nonnegative().optional(), - // tags: z.array() -}); export function QuizForm() { const navigate = useNavigate(); const { countries } = useCountries(); @@ -30,22 +22,24 @@ export function QuizForm() { user: { role }, } = useUserStore(); - const form = useForm>({ - resolver: zodResolver(formSchema), + const form = useForm>({ + resolver: zodResolver(quizFormSchema), defaultValues: { title: "", description: "", country: "", image: "", - // questions: [], + questions: [{ question: "", type: QuestionType.Single, options: [{ text: "", correct: false }] }], difficulty: Difficulty.Unknown, timeLimit: 0, + tags: [], }, }); const { register, handleSubmit, reset, + getValues, formState: { errors }, } = form; @@ -63,15 +57,13 @@ export function QuizForm() { }, }); - const onSubmit: SubmitHandler> = async (values, event) => { + const onSubmit: SubmitHandler> = async (values, event) => { event?.preventDefault(); try { await createQuizMutation({ variables: { quiz: { ...values, - questions: [], - tags: [], }, }, }); @@ -82,7 +74,7 @@ export function QuizForm() { return ( -
+