Skip to content
Open
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
16 changes: 16 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down
14 changes: 14 additions & 0 deletions src/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
22 changes: 22 additions & 0 deletions src/actions/exams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
44 changes: 44 additions & 0 deletions src/actions/question-bank.ts
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 5 additions & 1 deletion src/app/(main)/(non-exam-section)/available-exams/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <AvailableExams exams={data} />
const customExamdata = await getQuestionBankExam()

const updatedData = customExamdata ? [customExamdata, ...data] : data
return <AvailableExams exams={updatedData} />
}

export default Page
32 changes: 32 additions & 0 deletions src/app/(main)/(non-exam-section)/question-bank/page.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<div className='space-y-4 flex flex-col min-w-screen'>
<h1 className='text-4xl font-bold text-foreground'>Question Bank</h1>
<Tabs defaultValue='preview' className='w-[400px]'>
<TabsList className='w-96 flex justify-between '>
<TabsTrigger value='preview' className='flex-1'>
Question Bank
</TabsTrigger>
<TabsTrigger value='add' className='flex-1'>
Add Questions
</TabsTrigger>
</TabsList>
<TabsContent value='preview'>
<PreviewQuestion examId={customExam.id} />
</TabsContent>
<TabsContent value='add'>
<AddQuesitons examId={customExam.id} />
</TabsContent>
</Tabs>
</div>
) : (
'Can not fetch Question Bank!!'
)
}
8 changes: 5 additions & 3 deletions src/components/exams/avaiable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[] }) {
Expand Down Expand Up @@ -88,7 +88,9 @@ export default function AvailableExams({ exams }: { exams: Exam[] }) {
</div>
<div className='flex items-center text-foreground font-semibold'>
<CreditCard className='mr-2 h-4 w-4' />
<span>INR {exam.price}</span>
<span>
{exam.price === 0 ? 'FREE' : `INR ${exam.price}`}
</span>
</div>
</CardContent>
<CardFooter>
Expand Down
126 changes: 126 additions & 0 deletions src/components/question-bank/add-question.tsx
Original file line number Diff line number Diff line change
@@ -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<questionFormType>({
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 (
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<p className='text-base font-bold text-center text-blue-900 dark:text-purple-900'>
changing Tabs will result in loss of unsaved data.
</p>
<FormField
control={form.control}
name='text'
render={({ field }) => (
<FormItem>
<FormLabel />
<FormControl>
<Textarea
placeholder='Enter Your Questions Here'
{...field}
></Textarea>
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
)}
/>
<div className='flex flex-wrap gap-4'>
{optionsIndices.map((index) => (
<div key={index}>
<FormField
control={form.control}
name={`options.${index}`}
render={({ field }) => (
<FormItem>
<FormLabel>Option {index + 1}</FormLabel>
<FormControl>
<Input placeholder='Enter Here' {...field} />
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
)}
/>
</div>
))}
</div>
<div className=' mt-4 '>
<FormField
control={form.control}
name='correctAnswer'
render={({ field }) => (
<Select
onValueChange={(value) => field.onChange(parseInt(value, 10))}
>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder='Correct Option'
className='border p-2 '
/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value='0'>1</SelectItem>
<SelectItem value='1'>2</SelectItem>
<SelectItem value='2'>3</SelectItem>
<SelectItem value='3'>4</SelectItem>
</SelectContent>
</Select>
)}
/>
</div>
<div className='text-end'>
<Button type='submit' className='mt-4'>
Submit
</Button>
</div>
</form>
</Form>
</div>
)
}
Loading