From 0d77e924ba3b0c2494120e320e5939f9eeb1f98d Mon Sep 17 00:00:00 2001 From: Ashutoshpadhi629 Date: Mon, 26 Aug 2024 14:05:21 +0530 Subject: [PATCH] question-bank-added --- prisma/schema.prisma | 16 +++ src/actions/auth-actions.ts | 14 ++ src/actions/exams.ts | 22 +++ src/actions/question-bank.ts | 44 ++++++ .../available-exams/page.tsx | 6 +- .../(non-exam-section)/question-bank/page.tsx | 32 +++++ src/components/exams/avaiable.tsx | 8 +- src/components/question-bank/add-question.tsx | 126 ++++++++++++++++++ .../question-bank/preview_question.tsx | 115 ++++++++++++++++ src/components/ui/textarea.tsx | 24 ++++ src/config/index.ts | 11 +- src/schemas/index.ts | 14 +- 12 files changed, 425 insertions(+), 7 deletions(-) create mode 100644 src/actions/question-bank.ts create mode 100644 src/app/(main)/(non-exam-section)/question-bank/page.tsx create mode 100644 src/components/question-bank/add-question.tsx create mode 100644 src/components/question-bank/preview_question.tsx create mode 100644 src/components/ui/textarea.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e96fa7..8ed70ed 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,10 +25,23 @@ model User { passwordReset PasswordResetToken? examSubmissions ExamSubmission[] ExamProgress ExamProgress[] + QuestionBank QuestionBank? @@map("users") } +model QuestionBank { + id String @id @default(cuid()) + userId String @unique + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + examId String @unique + exam Exam @relation(fields: [examId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("question_banks") +} + enum Role { USER ADMIN @@ -95,11 +108,14 @@ model Exam { description String price Int duration Int + shuffleQues Boolean @default(false) + published Boolean @default(true) questions Question[] submissions ExamSubmission[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt ExamProgress ExamProgress[] + QuestionBank QuestionBank? @@map("exams") } diff --git a/src/actions/auth-actions.ts b/src/actions/auth-actions.ts index d53c715..1316cae 100644 --- a/src/actions/auth-actions.ts +++ b/src/actions/auth-actions.ts @@ -49,6 +49,20 @@ export const signUp = async (values: signUpValues) => { data: { username, email, hashedPassword: passwordHash, displayName }, }) + const customExam = await db.exam.create({ + data: { + title: 'Custom Test', + description: 'This is a custom Test from your own Question Bank', + duration: 120, + price: 0, + published: false, + }, + }) + + await db.questionBank.create({ + data: { userId: user.id, examId: customExam.id }, + }) + const code = generateVerificationCode() await db.verificationEmail.create({ diff --git a/src/actions/exams.ts b/src/actions/exams.ts index 1abdd3b..4e76eb8 100644 --- a/src/actions/exams.ts +++ b/src/actions/exams.ts @@ -7,6 +7,9 @@ import { cache } from 'react' export const getExams = cache(async () => { const response = await db.exam.findMany({ + where: { + published: true, + }, select: { id: true, title: true, @@ -17,6 +20,25 @@ export const getExams = cache(async () => { }) return response }) +export const getExamsQues = cache(async (examId: string) => { + const response = await db.exam.findUnique({ + where: { + id: examId, + }, + select: { + questions: { + select: { + id: true, + text: true, + correctAnswer: true, + options: true, + }, + }, + }, + }) + + return response +}) interface SubmitExamParams { examId: string diff --git a/src/actions/question-bank.ts b/src/actions/question-bank.ts new file mode 100644 index 0000000..fcc4552 --- /dev/null +++ b/src/actions/question-bank.ts @@ -0,0 +1,44 @@ +'use server' +import { validateRequest } from '@/auth' +import db from '@/lib/db' +import type { questionFormType } from '@/schemas' + +export const getQuestionBankExam = async () => { + const session = await validateRequest() + if (!session || !session.user) { + throw new Error('Unauthorized') + } + const questionBankExam = await db.questionBank.findUnique({ + where: { + userId: session.user.id, + }, + include: { + exam: true, + }, + }) + return questionBankExam?.exam || null +} + +export const addQuestionsToBank = async ( + examId: string, + question: questionFormType +) => { + const newQuestion = await db.question.create({ + data: { + examId: examId, + text: question.text, + options: question.options, + correctAnswer: question.correctAnswer, + }, + }) + return newQuestion +} + +export const deleteQuestion = async (questionid: string) => { + const question = await db.question.delete({ + where: { + id: questionid, + }, + }) + return question +} diff --git a/src/app/(main)/(non-exam-section)/available-exams/page.tsx b/src/app/(main)/(non-exam-section)/available-exams/page.tsx index e7b8413..2058d7e 100644 --- a/src/app/(main)/(non-exam-section)/available-exams/page.tsx +++ b/src/app/(main)/(non-exam-section)/available-exams/page.tsx @@ -1,10 +1,14 @@ import { getExams } from '@/actions/exams' +import { getQuestionBankExam } from '@/actions/question-bank' import AvailableExams from '@/components/exams/avaiable' import React from 'react' const Page = async () => { const data = await getExams() - return + const customExamdata = await getQuestionBankExam() + + const updatedData = customExamdata ? [customExamdata, ...data] : data + return } export default Page diff --git a/src/app/(main)/(non-exam-section)/question-bank/page.tsx b/src/app/(main)/(non-exam-section)/question-bank/page.tsx new file mode 100644 index 0000000..b0ce34b --- /dev/null +++ b/src/app/(main)/(non-exam-section)/question-bank/page.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { PreviewQuestion } from '@/components/question-bank/preview_question' +import { AddQuesitons } from '@/components/question-bank/add-question' +import { getQuestionBankExam } from '@/actions/question-bank' + +export default async function Home() { + const customExam = await getQuestionBankExam() + return customExam ? ( +
+

Question Bank

+ + + + Question Bank + + + Add Questions + + + + + + + + + +
+ ) : ( + 'Can not fetch Question Bank!!' + ) +} diff --git a/src/components/exams/avaiable.tsx b/src/components/exams/avaiable.tsx index 9312875..357f2c1 100644 --- a/src/components/exams/avaiable.tsx +++ b/src/components/exams/avaiable.tsx @@ -18,9 +18,9 @@ import { interface Exam { id: string title: string + duration: number | null description: string - duration: number - price: number + price: number | null } export default function AvailableExams({ exams }: { exams: Exam[] }) { @@ -88,7 +88,9 @@ export default function AvailableExams({ exams }: { exams: Exam[] }) {
- INR {exam.price} + + {exam.price === 0 ? 'FREE' : `INR ${exam.price}`} +
diff --git a/src/components/question-bank/add-question.tsx b/src/components/question-bank/add-question.tsx new file mode 100644 index 0000000..d90d784 --- /dev/null +++ b/src/components/question-bank/add-question.tsx @@ -0,0 +1,126 @@ +'use client' +import { QuestionFormSchema, questionFormType } from '@/schemas' +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../ui/form' +import { Textarea } from '../ui/textarea' +import { Button } from '../ui/button' +import { Input } from '../ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectValue, + SelectTrigger, +} from '../ui/select' +import { addQuestionsToBank } from '@/actions/question-bank' +import { toast } from 'sonner' + +export function AddQuesitons({ examId }: { examId: string }) { + const form = useForm({ + resolver: zodResolver(QuestionFormSchema), + defaultValues: { + text: '', + options: ['', '', '', ''], + }, + }) + + const onSubmit = async (data: questionFormType) => { + try { + const res = await addQuestionsToBank(examId, data) + toast.success('Question Saved') + form.reset() + } catch (error) { + toast.error('failed! PLease Try Again Later') + } + } + const optionsIndices = Array.from({ length: 4 }, (_, index) => index) + + return ( +
+
+ +

+ changing Tabs will result in loss of unsaved data. +

+ ( + + + + + + + + + )} + /> +
+ {optionsIndices.map((index) => ( +
+ ( + + Option {index + 1} + + + + + + + )} + /> +
+ ))} +
+
+ ( + + )} + /> +
+
+ +
+ + +
+ ) +} diff --git a/src/components/question-bank/preview_question.tsx b/src/components/question-bank/preview_question.tsx new file mode 100644 index 0000000..f81d7b5 --- /dev/null +++ b/src/components/question-bank/preview_question.tsx @@ -0,0 +1,115 @@ +'use client' +import { getExamsQues } from '@/actions/exams' +import { useEffect, useState } from 'react' +import { Button } from '../ui/button' +import { ScrollArea } from '@/components/ui/scroll-area' +import { RefreshCcw } from 'lucide-react' +import { Card } from '../ui/card' +import { toast } from 'sonner' +import { deleteQuestion } from '@/actions/question-bank' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog' + +export function PreviewQuestion({ examId }: { examId: string }) { + interface Questions { + questions: { + id: string + text: string + correctAnswer: number + options: string[] + }[] + } + const [isloading, setIsLoading] = useState(true) + const [Exam, setExam] = useState() + useEffect(() => { + async function getExams() { + const ques = await getExamsQues(examId) + setExam(ques) + setIsLoading(false) + } + getExams() + }, [examId]) + const Delete = async (questionId: string) => { + try { + const ques = await deleteQuestion(questionId) + toast.success('Question Deleted SuccessFully') + return ques + } catch (err) { + toast.error('Cannot Delete Now') + } + return + } + return isloading ? ( +
+ +
+ ) : Exam ? ( +
+ +

+ Please Relode to see the Latest Changes! +

+ {Exam.questions.map((p) => ( + +
+ + +
+ Delete +
+
+ + + + Are you absolutely sure you want to Delete this question? + + + This action cannot be undone. This will permanently delete + your Question and remove your data from our servers. + + + + Cancel + Delete(p.id)}> + Continue + + + +
+
+

{p.text}

+

+ {p.options.map((o, index) => ( +

    +
  • + {index + 1}: {o} +
  • +
+ ))} +

+
+ ))} +
+
+ ) : ( +
Please Add some Questions First
+ ) +} diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 0000000..9f9a6dc --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +