Guest User

Untitled

a guest
Jan 26th, 2025
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use client";
  2.  
  3. import { useState } from "react";
  4. import { useForm, useFieldArray, Controller } from "react-hook-form";
  5. import { zodResolver } from "@hookform/resolvers/zod";
  6. import { z } from "zod";
  7. import { PlusCircle, X } from "lucide-react";
  8. import { Button } from "@/components/ui/button";
  9. import { Input } from "@/components/ui/input";
  10. import { Textarea } from "@/components/ui/textarea";
  11. import { Label } from "@/components/ui/label";
  12. import { Checkbox } from "@/components/ui/checkbox";
  13. import { useToast } from "@/hooks/use-toast";
  14. import { createTest } from "@/app/actions";
  15. import type { Test } from "@/types/test";
  16.  
  17. const AnswerSchema = z.object({
  18.   text: z.string().trim().min(1, "Answer text is required"),
  19.   isCorrect: z.boolean(),
  20. });
  21.  
  22. const QuestionSchema = z.object({
  23.   text: z.string().min(1, "Question text is required"),
  24.   answers: z.array(AnswerSchema).min(1, "At least one answer is required"),
  25. });
  26.  
  27. const TestSchema = z.object({
  28.   testName: z.string().min(1, "Test name is required"),
  29.   testDescription: z.string().min(1, "Test description is required"),
  30.   questions: z
  31.     .array(QuestionSchema)
  32.     .min(1, "At least one question is required"),
  33. });
  34.  
  35. export default function TestCreationForm() {
  36.   const { toast } = useToast();
  37.   const [isSubmitting, setIsSubmitting] = useState(false);
  38.  
  39.   const {
  40.     control,
  41.     handleSubmit,
  42.     formState: { errors },
  43.   } = useForm<Test>({
  44.     resolver: zodResolver(TestSchema),
  45.     defaultValues: {
  46.       testName: "",
  47.       testDescription: "",
  48.       questions: [{ text: "", answers: [{ text: "", isCorrect: false }] }],
  49.     },
  50.   });
  51.  
  52.   const {
  53.     fields: questionFields,
  54.     append: appendQuestion,
  55.     remove: removeQuestion,
  56.   } = useFieldArray({
  57.     control,
  58.     name: "questions",
  59.   });
  60.  
  61.   const onFormSubmit = async (data: Test) => {
  62.     setIsSubmitting(true);
  63.     const formData = new FormData();
  64.     formData.append("testData", JSON.stringify(data));
  65.  
  66.     try {
  67.       const result = await createTest(formData);
  68.       if (result.success) {
  69.         toast({
  70.           title: "Success",
  71.           description: result.message,
  72.         });
  73.       } else {
  74.         toast({
  75.           title: "Error",
  76.           description: result.message,
  77.           variant: "destructive",
  78.         });
  79.       }
  80.     } catch (error) {
  81.       toast({
  82.         title: "Error",
  83.         description: "An unexpected error occurred",
  84.         variant: "destructive",
  85.       });
  86.     } finally {
  87.       setIsSubmitting(false);
  88.     }
  89.   };
  90.  
  91.   return (
  92.     <form
  93.       onSubmit={handleSubmit(onFormSubmit)}
  94.       className="space-y-6 max-w-2xl mx-auto p-6"
  95.     >
  96.       <div className="space-y-2">
  97.         <Label htmlFor="testName">Test Name</Label>
  98.         <Controller
  99.           name="testName"
  100.           control={control}
  101.           render={({ field }) => <Input {...field} id="testName" />}
  102.         />
  103.         {errors.testName && (
  104.           <p className="text-red-500 text-sm">{errors.testName.message}</p>
  105.         )}
  106.       </div>
  107.  
  108.       <div className="space-y-2">
  109.         <Label htmlFor="testDescription">Test Description</Label>
  110.         <Controller
  111.           name="testDescription"
  112.           control={control}
  113.           render={({ field }) => <Textarea {...field} id="testDescription" />}
  114.         />
  115.         {errors.testDescription && (
  116.           <p className="text-red-500 text-sm">
  117.             {errors.testDescription.message}
  118.           </p>
  119.         )}
  120.       </div>
  121.  
  122.       <div className="space-y-4">
  123.         {questionFields.map((questionField, questionIndex) => (
  124.           <div key={questionIndex} className="border p-4 rounded-md space-y-2">
  125.             <div className="flex items-center space-x-2">
  126.               <Controller
  127.                 name={`questions.${questionIndex}.text`}
  128.                 control={control}
  129.                 render={({ field }) => (
  130.                   <Input {...field} placeholder="Enter question" />
  131.                 )}
  132.               />
  133.               <Button
  134.                 type="button"
  135.                 variant="ghost"
  136.                 size="icon"
  137.                 onClick={() => removeQuestion(questionIndex)}
  138.               >
  139.                 <X className="h-4 w-4" />
  140.                 <span className="sr-only">Remove question</span>
  141.               </Button>
  142.             </div>
  143.             {errors.questions?.[questionIndex]?.text && (
  144.               <p className="text-red-500 text-sm">
  145.                 {errors.questions[questionIndex]?.text?.message}
  146.               </p>
  147.             )}
  148.  
  149.             <Controller
  150.               name={`questions.${questionIndex}.answers`}
  151.               control={control}
  152.               render={({ field }) => (
  153.                 <div className="space-y-2">
  154.                   {field.value.map((answer, answerIndex) => (
  155.                     <div
  156.                       key={answerIndex}
  157.                       className="flex items-center space-x-2 ml-4"
  158.                     >
  159.                       <Controller
  160.                         name={`questions.${questionIndex}.answers.${answerIndex}.isCorrect`}
  161.                         control={control}
  162.                         render={({ field: checkboxField }) => (
  163.                           <Checkbox
  164.                             id={`question-${questionIndex}-answer-${answerIndex}-correct`}
  165.                             checked={checkboxField.value}
  166.                             onCheckedChange={checkboxField.onChange}
  167.                           />
  168.                         )}
  169.                       />
  170.                       <Controller
  171.                         name={`questions.${questionIndex}.answers.${answerIndex}.text`}
  172.                         control={control}
  173.                         render={({ field: inputField }) => (
  174.                           <Input {...inputField} placeholder="Enter answer" />
  175.                         )}
  176.                       />
  177.                       <Button
  178.                         type="button"
  179.                         variant="ghost"
  180.                         size="icon"
  181.                         onClick={() => {
  182.                           const newAnswers = [...field.value];
  183.                           newAnswers.splice(answerIndex, 1);
  184.                           field.onChange(newAnswers);
  185.                         }}
  186.                       >
  187.                         <X className="h-4 w-4" />
  188.                         <span className="sr-only">Remove answer</span>
  189.                       </Button>
  190.                     </div>
  191.                   ))}
  192.                   <Button
  193.                     type="button"
  194.                     variant="outline"
  195.                     size="sm"
  196.                     onClick={() =>
  197.                       field.onChange([
  198.                         ...field.value,
  199.                         { text: "", isCorrect: false },
  200.                       ])
  201.                     }
  202.                     className="mt-2"
  203.                   >
  204.                     <PlusCircle className="h-4 w-4 mr-2" />
  205.                     Add Answer
  206.                   </Button>
  207.                 </div>
  208.               )}
  209.             />
  210.             {errors.questions?.[questionIndex]?.answers && (
  211.               <p className="text-red-500 text-sm">
  212.                 {errors.questions[questionIndex]?.answers?.message}
  213.               </p>
  214.             )}
  215.           </div>
  216.         ))}
  217.       </div>
  218.  
  219.       <Button
  220.         type="button"
  221.         onClick={() =>
  222.           appendQuestion({
  223.             text: "",
  224.             answers: [{ text: "", isCorrect: false }],
  225.           })
  226.         }
  227.         variant="outline"
  228.         className="w-full"
  229.       >
  230.         <PlusCircle className="h-4 w-4 mr-2" />
  231.         Add Question
  232.       </Button>
  233.  
  234.       <Button type="submit" className="w-full" disabled={isSubmitting}>
  235.         {isSubmitting ? "Submitting..." : "Submit Test"}
  236.       </Button>
  237.     </form>
  238.   );
  239. }
  240.  
Advertisement
Add Comment
Please, Sign In to add comment