├── app ├── favicon.ico ├── sign-in │ └── [[...sign-in]] │ │ └── page.tsx ├── sign-up │ └── [[...sign-up]] │ │ └── page.tsx ├── api │ ├── categories │ │ └── route.ts │ └── user │ │ ├── route.ts │ │ ├── register │ │ └── route.ts │ │ └── quiz │ │ ├── start │ │ └── route.ts │ │ └── finish │ │ └── route.tsx ├── page.tsx ├── stats │ └── page.tsx ├── layout.tsx ├── results │ └── page.tsx ├── globals.css ├── categories │ └── [categoryId] │ │ └── page.tsx └── quiz │ ├── setup │ └── [quizId] │ │ └── page.tsx │ └── page.tsx ├── public ├── user.png ├── icon--logo-lg.png ├── sounds │ └── error.mp3 ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg ├── icon--logo.svg ├── logo--quizzer.svg ├── next.svg └── categories │ ├── image--timeline.svg │ ├── image--computer-science-basics.svg │ └── image--science.svg ├── utils ├── formatTime.ts ├── connect.ts └── Icons.tsx ├── postcss.config.mjs ├── lib ├── utils.ts └── arcject.ts ├── next.config.ts ├── components ├── Loader.tsx ├── ui │ ├── label.tsx │ ├── input.tsx │ ├── card.tsx │ ├── button.tsx │ ├── table.tsx │ ├── select.tsx │ └── chart.tsx ├── Countdown.tsx ├── quiz │ ├── HomeCard.tsx │ └── QuizCard.tsx ├── Header.tsx ├── CategoryBarChart.tsx └── UserStats.tsx ├── providers └── ContextProvider.tsx ├── components.json ├── .gitignore ├── tsconfig.json ├── context ├── useCategories.js └── globalContext.js ├── scripts ├── questions.ts ├── quizzes.ts └── categories.ts ├── types └── types.ts ├── package.json ├── README.md ├── tailwind.config.ts ├── middleware.ts ├── prisma └── schema.prisma └── data ├── csQuestions.js ├── chemistryQuestions.js ├── physicsQuestions.js ├── biologyQuestions.js ├── programmingQuestions.js └── dsQuestions.js /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maclinz/kwizi/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maclinz/kwizi/HEAD/public/user.png -------------------------------------------------------------------------------- /public/icon--logo-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maclinz/kwizi/HEAD/public/icon--logo-lg.png -------------------------------------------------------------------------------- /public/sounds/error.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maclinz/kwizi/HEAD/public/sounds/error.mp3 -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/formatTime.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | 3 | export const formatTime = (date: Date) => { 4 | return moment(date).format("Do MMMM YYYY, HH:mm"); 5 | }; 6 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | domains: ["img.clerk.com"], 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | import React from "react"; 3 | 4 | function page() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default page; 13 | -------------------------------------------------------------------------------- /app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | import React from "react"; 3 | 4 | function page() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default page; 13 | -------------------------------------------------------------------------------- /components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Loader() { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | } 10 | 11 | export default Loader; 12 | -------------------------------------------------------------------------------- /providers/ContextProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { GlobalContextProvider } from "@/context/globalContext"; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | } 8 | 9 | function ContextProvider({ children }: Props) { 10 | return {children}; 11 | } 12 | 13 | export default ContextProvider; 14 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/connect.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | let prisma: PrismaClient; 4 | 5 | declare global { 6 | var prisma: PrismaClient; 7 | } 8 | 9 | if (process.env.NODE_ENV === "production") { 10 | prisma = new PrismaClient(); 11 | } else { 12 | if (!global.prisma) { 13 | global.prisma = new PrismaClient(); 14 | } 15 | 16 | prisma = global.prisma; 17 | } 18 | 19 | export default prisma; 20 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /lib/arcject.ts: -------------------------------------------------------------------------------- 1 | import arcjet, { tokenBucket } from "@arcjet/next"; 2 | 3 | export const aj = arcjet({ 4 | key: process.env.ARCJET_KEY!, 5 | characteristics: ["userId"], 6 | rules: [ 7 | // Create a token bucket rate limit. Other algorithms are supported. 8 | tokenBucket({ 9 | mode: "LIVE", // will block requests. Use "DRY_RUN" to log only 10 | refillRate: 2, // refill 2 tokens per interval 11 | interval: 10, // refill every 10 seconds 12 | capacity: 20, // bucket maximum capacity of 20 tokens 13 | }), 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /app/api/categories/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import prisma from "@/utils/connect"; 3 | 4 | export async function GET(req: NextRequest) { 5 | try { 6 | // Get all categories 7 | const categories = await prisma.category.findMany({}); 8 | 9 | return NextResponse.json(categories); 10 | } catch (error) { 11 | console.log("There was an error getting Categories: ", error); 12 | return NextResponse.json( 13 | { error: "There was an error getting Categories" }, 14 | { 15 | status: 500, 16 | } 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import HomeCard from "@/components/quiz/HomeCard"; 4 | import { useGlobalContext } from "@/context/globalContext"; 5 | import { ICategory } from "@/types/types"; 6 | 7 | export default function Home() { 8 | const { categories } = useGlobalContext(); 9 | return ( 10 |
11 |

Quiz Catelog

12 | 13 |
14 | {categories.map((category: ICategory) => ( 15 | 16 | ))} 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /context/useCategories.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect } from "react"; 3 | import { useState } from "react"; 4 | 5 | const useCategories = () => { 6 | const [loading, setLoading] = useState(true); 7 | const [categories, setCategories] = useState([]); 8 | 9 | const getCategories = async () => { 10 | setLoading(true); 11 | try { 12 | const res = await axios.get("/api/categories"); 13 | 14 | setCategories(res.data); 15 | 16 | setLoading(false); 17 | } catch (error) { 18 | console.log("Error fetching categories", error); 19 | } 20 | }; 21 | 22 | useEffect(() => { 23 | getCategories(); 24 | }, []); 25 | 26 | return { loading, categories }; 27 | }; 28 | 29 | export default useCategories; 30 | -------------------------------------------------------------------------------- /app/api/user/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function GET(req: NextRequest) { 5 | try { 6 | const { userId } = await auth(); 7 | 8 | if (!userId) { 9 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 10 | } 11 | 12 | // find the user in the db 13 | const user = await prisma.user.findUnique({ 14 | where: { clerkId: userId }, 15 | }); 16 | 17 | if (!user) { 18 | return NextResponse.json({ error: "User not found" }, { status: 404 }); 19 | } 20 | 21 | return NextResponse.json(user); 22 | } catch (error) { 23 | console.log("Error starting quiz: ", error); 24 | return NextResponse.json({ error: "Error getting user" }, { status: 500 }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /components/Countdown.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | interface Props { 5 | intitialTimeLeft: number; 6 | } 7 | 8 | function Countdown({ intitialTimeLeft }: Props) { 9 | const [timeLeft, setTimeLeft] = React.useState(intitialTimeLeft); 10 | 11 | useEffect(() => { 12 | const timer = setInterval(() => { 13 | setTimeLeft((prev) => { 14 | if (prev <= 1) { 15 | clearInterval(timer); 16 | window.location.reload(); 17 | } 18 | 19 | return prev - 1; 20 | }); 21 | }, 1000); 22 | 23 | return () => clearInterval(timer); 24 | }, []); 25 | 26 | return ( 27 |

28 | Please try again in{" "} 29 | {timeLeft}{" "} 30 | seconds. This page will automatically refresh. 31 |

32 | ); 33 | } 34 | 35 | export default Countdown; 36 | -------------------------------------------------------------------------------- /utils/Icons.tsx: -------------------------------------------------------------------------------- 1 | // Get the images here! 2 | // https://storyset.com/ 3 | // https://undraw.co/illustrations 4 | 5 | export const home = ; 6 | export const chart = ; 7 | export const check = ; 8 | export const logout = ; 9 | export const login = ; 10 | export const list = ; 11 | export const dots = ; 12 | export const checkAbc = ; 13 | export const flag = ; 14 | export const crosshairs = ; 15 | export const next = ; 16 | export const play = ; 17 | -------------------------------------------------------------------------------- /app/stats/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import { error } from "console"; 3 | import React from "react"; 4 | import prisma from "@/utils/connect"; 5 | import UserStats from "@/components/UserStats"; 6 | 7 | async function page() { 8 | const { userId } = await auth(); 9 | 10 | if (!userId) { 11 | return { error: "You need to be logged in to view this page" }; 12 | } 13 | 14 | // get user data --> populate the categoryStats using the category 15 | 16 | const user = await prisma.user.findUnique({ 17 | where: { 18 | clerkId: userId, 19 | }, 20 | include: { 21 | categoryStats: { 22 | include: { 23 | category: true, // populate the category 24 | }, 25 | }, 26 | }, 27 | }); 28 | 29 | console.log("User stats:", user); 30 | 31 | return ( 32 |
33 | 34 |
35 | ); 36 | } 37 | 38 | export default page; 39 | -------------------------------------------------------------------------------- /app/api/user/register/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function POST(req: NextRequest) { 5 | try { 6 | const { userId } = await auth(); 7 | 8 | if (!userId) { 9 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 10 | } 11 | 12 | // check if the user exits in the db 13 | let user = await prisma.user.findUnique({ 14 | where: { clerkId: userId }, 15 | }); 16 | 17 | //if user does not exist, create a new user 18 | 19 | if (!user) { 20 | user = await prisma.user.create({ 21 | data: { 22 | clerkId: userId, 23 | }, 24 | }); 25 | } else { 26 | console.log("User already exists"); 27 | } 28 | 29 | return NextResponse.json(user); 30 | } catch (error) { 31 | console.log("Error starting quiz: ", error); 32 | return NextResponse.json({ error: "Error creating user" }, { status: 500 }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/questions.ts: -------------------------------------------------------------------------------- 1 | const questions = require("../data/programmingQuestions.js"); 2 | 3 | let questionsPrisma: any; 4 | 5 | async function seedQuestions() { 6 | const { PrismaClient } = require("@prisma/client"); 7 | 8 | questionsPrisma = new PrismaClient(); 9 | 10 | console.log("Seeding questions..."); 11 | 12 | for (const question of questions) { 13 | const createdQuestion = await questionsPrisma.question.create({ 14 | data: { 15 | text: question.text, 16 | quizId: "6764ab1258d3aabee390cc72", 17 | options: { 18 | create: question.options, 19 | }, 20 | difficulty: question.difficulty, 21 | }, 22 | }); 23 | 24 | console.log(`Created question: ${createdQuestion.text}`); 25 | } 26 | console.log("Seeding questions completed."); 27 | } 28 | 29 | seedQuestions() 30 | .catch((e) => { 31 | console.log("Error seeding questions: ", e); 32 | }) 33 | .finally(async () => { 34 | await questionsPrisma.$disconnect(); 35 | }); 36 | -------------------------------------------------------------------------------- /types/types.ts: -------------------------------------------------------------------------------- 1 | interface ICategory { 2 | id: string; 3 | name: string; 4 | description: string; 5 | image?: string; 6 | quizzes: IQuiz[]; 7 | } 8 | 9 | interface IQuiz { 10 | id: string; 11 | title: string; 12 | description?: string | null; 13 | image?: string | null; 14 | categoryId: string; 15 | questions: IQuestion[]; 16 | } 17 | 18 | interface IQuestion { 19 | id: string; 20 | text: string; 21 | difficulty?: string | null; 22 | options: IOption[]; 23 | } 24 | 25 | interface IResponse { 26 | questionId: string; 27 | optionId: string; 28 | isCorrect: boolean; 29 | } 30 | 31 | interface IOption { 32 | id: string; 33 | text: string; 34 | isCorrect: boolean; 35 | } 36 | 37 | interface ICategoryStats { 38 | attempts: number; 39 | averageScore: number | null; 40 | categoryId: string; 41 | completed: number; 42 | id: string; 43 | lastAttempt: Date; 44 | userId: string; 45 | category: ICategory; 46 | } 47 | 48 | export type { ICategory, IQuiz, IQuestion, IOption, IResponse, ICategoryStats }; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kwizi", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "npx prisma generate" 11 | }, 12 | "dependencies": { 13 | "@arcjet/next": "^1.0.0-alpha.34", 14 | "@clerk/nextjs": "^6.9.5", 15 | "@prisma/client": "^6.1.0", 16 | "@radix-ui/react-label": "^2.1.1", 17 | "@radix-ui/react-select": "^2.1.4", 18 | "@radix-ui/react-slot": "^1.1.1", 19 | "axios": "^1.7.9", 20 | "class-variance-authority": "^0.7.1", 21 | "clsx": "^2.1.1", 22 | "lucide-react": "^0.468.0", 23 | "moment": "^2.30.1", 24 | "next": "15.1.2", 25 | "prisma": "^6.1.0", 26 | "react": "^19.0.0", 27 | "react-dom": "^19.0.0", 28 | "react-hot-toast": "^2.4.1", 29 | "recharts": "^2.15.0", 30 | "tailwind-merge": "^2.5.5", 31 | "tailwindcss-animate": "^1.0.7" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^20", 35 | "@types/react": "^19", 36 | "@types/react-dom": "^19", 37 | "postcss": "^8", 38 | "tailwindcss": "^3.4.1", 39 | "typescript": "^5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/icon--logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/logo--quizzer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/quiz/HomeCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ICategory } from "@/types/types"; 3 | import Image from "next/image"; 4 | import { useRouter } from "next/navigation"; 5 | import React from "react"; 6 | 7 | interface Props { 8 | category: ICategory; 9 | } 10 | 11 | function HomeCard({ category }: Props) { 12 | const router = useRouter(); 13 | 14 | return ( 15 |
router.push(`/categories/${category.id}`)} 19 | > 20 |
21 | {category.name} 35 |
36 | 37 |
38 |
39 |

{category.name}

40 |

41 | {category.description} 42 |

43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | export default HomeCard; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { ClerkProvider } from "@clerk/nextjs"; 4 | import Header from "@/components/Header"; 5 | import { Nunito } from "next/font/google"; 6 | import ContextProvider from "@/providers/ContextProvider"; 7 | import { Toaster } from "react-hot-toast"; 8 | 9 | export const metadata: Metadata = { 10 | title: "Create Next App", 11 | description: "Generated by create next app", 12 | }; 13 | 14 | const nunito = Nunito({ 15 | subsets: ["latin"], 16 | weight: ["300", "400", "500", "600", "700"], 17 | variable: "--font-nunito", 18 | }); 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 | {children} 43 |
44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /app/api/user/quiz/start/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function POST(req: NextRequest) { 5 | const { userId: clerkId } = await auth(); 6 | const { categoryId } = await req.json(); 7 | 8 | if (!clerkId) { 9 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 10 | } 11 | 12 | try { 13 | const user = await prisma.user.findUnique({ 14 | where: { clerkId }, 15 | }); 16 | 17 | if (!user) { 18 | return NextResponse.json({ error: "User not found" }, { status: 404 }); 19 | } 20 | 21 | const userId = user.id; 22 | 23 | // find or create a categoryStat entry 24 | 25 | let stat = await prisma.categoryStat.findUnique({ 26 | where: { 27 | userId_categoryId: { 28 | categoryId, 29 | userId, 30 | }, 31 | }, 32 | }); 33 | 34 | if (!stat) { 35 | stat = await prisma.categoryStat.create({ 36 | data: { 37 | userId, 38 | categoryId, 39 | attempts: 1, 40 | lastAttempt: new Date(), 41 | }, 42 | }); 43 | } else { 44 | await prisma.categoryStat.update({ 45 | where: { 46 | userId_categoryId: { 47 | userId, 48 | categoryId, 49 | }, 50 | }, 51 | data: { 52 | attempts: stat.attempts + 1, 53 | lastAttempt: new Date(), 54 | }, 55 | }); 56 | } 57 | 58 | return NextResponse.json(stat); 59 | } catch (error) { 60 | console.log("Error starting quiz: ", error); 61 | return NextResponse.json({ error: "Error starting quiz" }, { status: 500 }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/quizzes.ts: -------------------------------------------------------------------------------- 1 | const { PrismaClient } = require("@prisma/client"); 2 | 3 | let quizzesPrisma: any; 4 | 5 | const quizzes = [ 6 | { 7 | title: "Computer Science Basics", 8 | description: "A quiz about fundamental computer science concepts.", 9 | categoryId: "676482a0a9fd4923c30ff2d7", // Replace with the actual category ID 10 | }, 11 | { 12 | title: "Programming Fundamentals", 13 | description: "Test your knowledge of basic programming concepts.", 14 | categoryId: "6764829fa9fd4923c30ff2d6", 15 | }, 16 | { 17 | title: "Data Structures", 18 | description: "Assess your understanding of data structures.", 19 | categoryId: "676482a0a9fd4923c30ff2d7", 20 | }, 21 | { 22 | title: "Physics", 23 | description: "Test your knowledge of physics", 24 | categoryId: "6764829fa9fd4923c30ff2d4", 25 | }, 26 | { 27 | title: "Biology", 28 | description: "Test your knowledge of physics", 29 | categoryId: "6764829fa9fd4923c30ff2d4", 30 | }, 31 | { 32 | title: "Chemistry", 33 | description: "Test your knowledge of physics", 34 | categoryId: "6764829fa9fd4923c30ff2d4", 35 | }, 36 | ]; 37 | 38 | async function seedQuizzes() { 39 | quizzesPrisma = new PrismaClient(); 40 | 41 | console.log("Seeding quizzes..."); 42 | 43 | for (const quiz of quizzes) { 44 | const craetedQuiz = await quizzesPrisma.quiz.create({ 45 | data: quiz, 46 | }); 47 | 48 | console.log("Created quiz: ", `${craetedQuiz.title}`); 49 | } 50 | 51 | console.log("Seeding quizzes completed."); 52 | } 53 | 54 | seedQuizzes() 55 | .catch((e) => { 56 | console.log("Error seeding quizzes: ", e); 57 | }) 58 | .finally(async () => { 59 | await quizzesPrisma.$disconnect(); 60 | }); 61 | -------------------------------------------------------------------------------- /context/globalContext.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import useCategories from "./useCategories"; 3 | import { useUser } from "@clerk/nextjs"; 4 | import axios from "axios"; 5 | 6 | const GlobalContext = React.createContext(); 7 | 8 | export const GlobalContextProvider = ({ children }) => { 9 | const { loading, categories } = useCategories(); 10 | const { user, isLoaded } = useUser(); 11 | 12 | const [quizSetup, setQuizSetup] = React.useState({ 13 | questionCount: 1, 14 | category: null, 15 | difficulty: null, 16 | }); 17 | const [selectedQuiz, setSelectedQuiz] = React.useState(null); 18 | const [quizResponses, setQuizResponses] = React.useState([]); 19 | const [filteredQuestions, setFilteredQuestions] = React.useState([]); 20 | 21 | useEffect(() => { 22 | if (!isLoaded || !user?.emailAddresses[0]?.emailAddress) return; 23 | 24 | const registerUser = async () => { 25 | try { 26 | await axios.post("/api/user/register"); 27 | 28 | console.log("User registered successfully!"); 29 | } catch (error) { 30 | console.error("Error registering user:", error); 31 | } 32 | }; 33 | 34 | if (user?.emailAddresses[0]?.emailAddress) { 35 | registerUser(); 36 | } 37 | }, [user, isLoaded]); 38 | 39 | console.log("Filtered Questions:", filteredQuestions); 40 | 41 | return ( 42 | 56 | {children} 57 | 58 | ); 59 | }; 60 | 61 | export const useGlobalContext = () => { 62 | return React.useContext(GlobalContext); 63 | }; 64 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | darkMode: ["class"], 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: 'hsl(var(--background))', 14 | foreground: 'hsl(var(--foreground))', 15 | card: { 16 | DEFAULT: 'hsl(var(--card))', 17 | foreground: 'hsl(var(--card-foreground))' 18 | }, 19 | popover: { 20 | DEFAULT: 'hsl(var(--popover))', 21 | foreground: 'hsl(var(--popover-foreground))' 22 | }, 23 | primary: { 24 | DEFAULT: 'hsl(var(--primary))', 25 | foreground: 'hsl(var(--primary-foreground))' 26 | }, 27 | secondary: { 28 | DEFAULT: 'hsl(var(--secondary))', 29 | foreground: 'hsl(var(--secondary-foreground))' 30 | }, 31 | muted: { 32 | DEFAULT: 'hsl(var(--muted))', 33 | foreground: 'hsl(var(--muted-foreground))' 34 | }, 35 | accent: { 36 | DEFAULT: 'hsl(var(--accent))', 37 | foreground: 'hsl(var(--accent-foreground))' 38 | }, 39 | destructive: { 40 | DEFAULT: 'hsl(var(--destructive))', 41 | foreground: 'hsl(var(--destructive-foreground))' 42 | }, 43 | border: 'hsl(var(--border))', 44 | input: 'hsl(var(--input))', 45 | ring: 'hsl(var(--ring))', 46 | chart: { 47 | '1': 'hsl(var(--chart-1))', 48 | '2': 'hsl(var(--chart-2))', 49 | '3': 'hsl(var(--chart-3))', 50 | '4': 'hsl(var(--chart-4))', 51 | '5': 'hsl(var(--chart-5))' 52 | } 53 | }, 54 | borderRadius: { 55 | lg: 'var(--radius)', 56 | md: 'calc(var(--radius) - 2px)', 57 | sm: 'calc(var(--radius) - 4px)' 58 | } 59 | } 60 | }, 61 | plugins: [require("tailwindcss-animate")], 62 | } satisfies Config; 63 | -------------------------------------------------------------------------------- /scripts/categories.ts: -------------------------------------------------------------------------------- 1 | let categsPrisma: any; 2 | 3 | async function addCategories() { 4 | const { PrismaClient } = require("@prisma/client"); 5 | 6 | categsPrisma = new PrismaClient(); 7 | 8 | const categories = [ 9 | { 10 | name: "Science", 11 | description: 12 | "Science is the pursuit and application of knowledge and understanding of the natural and social world following a systematic methodology based on evidence.", 13 | }, 14 | { 15 | name: "Technology", 16 | description: "Dive into the latest technological advancements.", 17 | }, 18 | { 19 | name: "Programming", 20 | description: "Learn about coding and software development.", 21 | }, 22 | { 23 | name: "Computer Science", 24 | description: "Understand the fundamentals of computers and algorithms.", 25 | }, 26 | { 27 | name: "Mathematics", 28 | description: "Master the language of numbers and patterns.", 29 | }, 30 | { 31 | name: "History", 32 | description: "Discover the events that shaped our world.", 33 | }, 34 | { 35 | name: "Art", 36 | description: "Appreciate creativity through various forms of art.", 37 | }, 38 | { 39 | name: "Geography", 40 | description: "Explore the physical features of our planet.", 41 | }, 42 | { 43 | name: "Physics", 44 | description: "Unravel the laws governing the universe.", 45 | }, 46 | { name: "Biology", description: "Study the science of living organisms." }, 47 | ]; 48 | 49 | console.log("Adding Categories..."); 50 | 51 | for (const category of categories) { 52 | await categsPrisma.category.create({ 53 | data: category, 54 | }); 55 | } 56 | 57 | console.log("Categories Added Successfully!"); 58 | } 59 | 60 | addCategories() 61 | .catch((e) => { 62 | console.log("Error Adding Categories: ", e); 63 | }) 64 | .finally(async () => { 65 | await categsPrisma.$disconnect(); 66 | }); 67 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; 2 | import arcjet, { detectBot, shield } from "@arcjet/next"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export const config = { 6 | matcher: [ 7 | // Skip Next.js internals and all static files, unless found in search params 8 | "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", 9 | // Always run for API routes 10 | "/(api|trpc)(.*)", 11 | ], 12 | }; 13 | 14 | const aj = arcjet({ 15 | key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com 16 | rules: [ 17 | // shield protects agains common attacks such as SQL injection, XSS, etc 18 | shield( 19 | { mode: "LIVE" } // will block requests. Use "DRY_RUN" to log only 20 | ), 21 | 22 | detectBot({ 23 | mode: "LIVE", // will block requests. Use "DRY_RUN" to log only 24 | // Block all bots except the following 25 | allow: [ 26 | "CATEGORY:SEARCH_ENGINE", 27 | "CURL", 28 | "VERCEL_MONITOR_PREVIEW", // Vercel preview bot 29 | // Google, Bing, etc 30 | // Uncomment to allow these other common bot categories 31 | // See the full list at https://arcjet.com/bot-list 32 | //"CATEGORY:MONITOR", // Uptime monitoring services 33 | //"CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord 34 | ], 35 | }), 36 | ], 37 | }); 38 | 39 | // define public routes 40 | const isPublicRoute = createRouteMatcher([ 41 | "/", 42 | "/sign-in(.*)", 43 | "/sign-up(.*)", 44 | "/api/categories(.*)", 45 | ]); 46 | 47 | export default clerkMiddleware(async (auth, req) => { 48 | // run arcjet middleware first 49 | const decsion = await aj.protect(req); 50 | 51 | if (decsion.isDenied()) { 52 | return NextResponse.json( 53 | { error: "Forbidden", reason: decsion.reason }, 54 | { status: 403 } 55 | ); 56 | } 57 | 58 | // skip authenticaion for public routes 59 | if (isPublicRoute(req)) { 60 | return NextResponse.next(); 61 | } 62 | 63 | // authenticate user and protect non-public routes 64 | await auth.protect(); 65 | 66 | return NextResponse.next(); 67 | }); 68 | -------------------------------------------------------------------------------- /components/quiz/QuizCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useGlobalContext } from "@/context/globalContext"; 3 | import { IQuiz } from "@/types/types"; 4 | import { dots } from "@/utils/Icons"; 5 | import Image from "next/image"; 6 | import { useRouter } from "next/navigation"; 7 | import React from "react"; 8 | 9 | interface Props { 10 | quiz: IQuiz; 11 | } 12 | 13 | function QuizCard({ quiz }: Props) { 14 | const router = useRouter(); 15 | 16 | const { setSelectedQuiz } = useGlobalContext(); 17 | 18 | const handleClick = () => { 19 | setSelectedQuiz(quiz); 20 | router.push(`/quiz/setup/${quiz.id}`); 21 | }; 22 | 23 | return ( 24 |
29 |
30 |
31 | quiz image 45 |
46 | 47 |
48 |

{quiz.title}

49 |

50 | {quiz.description} 51 |

52 |
53 | 54 |
55 |

56 | {dots} 57 | 58 | Total Questions:{" "} 59 | 60 | {quiz.questions.length} 61 | 62 | 63 |

64 |
65 |
66 |
67 | ); 68 | } 69 | 70 | export default QuizCard; 71 | -------------------------------------------------------------------------------- /app/api/user/quiz/finish/route.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function POST(req: NextRequest) { 5 | try { 6 | const { userId: clerkId } = await auth(); 7 | 8 | if (!clerkId) { 9 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 10 | } 11 | 12 | const { categoryId, quizId, score, responses } = await req.json(); 13 | 14 | // validate the fields 15 | if ( 16 | !categoryId || 17 | !quizId || 18 | typeof score !== "number" || 19 | !Array.isArray(responses) 20 | ) { 21 | return NextResponse.json({ error: "Invalid request" }, { status: 400 }); 22 | } 23 | 24 | const user = await prisma.user.findUnique({ where: { clerkId } }); 25 | 26 | if (!user) { 27 | return NextResponse.json({ error: "User not found" }, { status: 404 }); 28 | } 29 | 30 | // fetch or create a categoryStat entry 31 | let stat = await prisma.categoryStat.findUnique({ 32 | where: { 33 | userId_categoryId: { 34 | userId: user.id, 35 | categoryId, 36 | }, 37 | }, 38 | }); 39 | 40 | if (stat) { 41 | // calculate the average score 42 | const totalScore = (stat.averageScore || 0) * stat.completed + score; 43 | const newAverageScore = totalScore / (stat.completed + 1); 44 | 45 | // update the categoryStat entry 46 | stat = await prisma.categoryStat.update({ 47 | where: { id: stat.id }, 48 | data: { 49 | completed: stat.completed + 1, 50 | averageScore: newAverageScore, 51 | lastAttempt: new Date(), 52 | }, 53 | }); 54 | } else { 55 | // create a new categoryStat entry 56 | stat = await prisma.categoryStat.create({ 57 | data: { 58 | userId: user.id, 59 | categoryId, 60 | attempts: 1, 61 | completed: 1, 62 | averageScore: score, 63 | lastAttempt: new Date(), 64 | }, 65 | }); 66 | } 67 | 68 | return NextResponse.json(stat); 69 | } catch (error) { 70 | console.log("Error finishing quiz: ", error); 71 | return NextResponse.json( 72 | { error: "Error finishing quiz" }, 73 | { status: 500 } 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | blue: "bg-blue-400 uppercase text-white shadow hover:bg-blue-500 shadow-[0_.3rem_0_0_#3b82f6] hover:shadow-[0_.3rem_0_0_#60a5fa]", 23 | green: 24 | "bg-[#60E100] uppercase text-white shadow hover:bg-[#51BF22] shadow-[0_.3rem_0_0_#51BF22] hover:shadow-[0_.3rem_0_0_#60E100]", 25 | }, 26 | size: { 27 | default: "h-9 px-4 py-2", 28 | sm: "h-8 rounded-md px-3 text-xs", 29 | lg: "h-10 rounded-md px-8", 30 | icon: "h-9 w-9", 31 | }, 32 | }, 33 | defaultVariants: { 34 | variant: "default", 35 | size: "default", 36 | }, 37 | } 38 | ); 39 | 40 | export interface ButtonProps 41 | extends React.ButtonHTMLAttributes, 42 | VariantProps { 43 | asChild?: boolean; 44 | } 45 | 46 | const Button = React.forwardRef( 47 | ({ className, variant, size, asChild = false, ...props }, ref) => { 48 | const Comp = asChild ? Slot : "button"; 49 | return ( 50 | 55 | ); 56 | } 57 | ); 58 | Button.displayName = "Button"; 59 | 60 | export { Button, buttonVariants }; 61 | -------------------------------------------------------------------------------- /app/results/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import { useGlobalContext } from "@/context/globalContext"; 4 | import { play } from "@/utils/Icons"; 5 | import { useRouter } from "next/navigation"; 6 | import React from "react"; 7 | 8 | function page() { 9 | const router = useRouter(); 10 | const { quizResponses, selectedQuiz } = useGlobalContext(); 11 | 12 | if (!quizResponses || quizResponses.length === 0) { 13 | return router.push("/"); /// redirect to home page 14 | } 15 | 16 | // calculate the score 17 | const correctAnswers = quizResponses.filter( 18 | (res: { isCorrect: boolean }) => res.isCorrect 19 | ).length; 20 | 21 | const totalQuestions = quizResponses.length; 22 | const scorePercentage = (correctAnswers / totalQuestions) * 100; 23 | 24 | // Show message for the score 25 | let message = ""; 26 | 27 | if (scorePercentage < 25) { 28 | message = "You need to try harder!"; 29 | } else if (scorePercentage >= 25 && scorePercentage < 50) { 30 | message = "You're getting there! Keep practicing."; 31 | } else if (scorePercentage >= 50 && scorePercentage < 75) { 32 | message = "Good effort! You're above average."; 33 | } else if (scorePercentage >= 75 && scorePercentage < 100) { 34 | message = "Great job! You're so close to perfect!"; 35 | } else if (scorePercentage === 100) { 36 | message = "Outstanding! You got everything right!"; 37 | } 38 | 39 | return ( 40 |
41 |

Quiz Results

42 | 43 |

44 | You scored {correctAnswers} out of{" "} 45 | {""} 46 | {totalQuestions} {""} 47 |

48 | 49 |

50 | {scorePercentage.toFixed()}% 51 |

52 | 53 |

{message}

54 | 55 |
56 | 63 |
64 |
65 | ); 66 | } 67 | 68 | export default page; 69 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | datasource db { 12 | provider = "mongodb" 13 | url = env("DATABASE_URL") 14 | } 15 | 16 | model User { 17 | id String @id @default(auto()) @map("_id") @db.ObjectId 18 | clerkId String @unique 19 | role String @default("user") 20 | categoryStats CategoryStat[] 21 | } 22 | 23 | model Category{ 24 | id String @id @default(auto()) @map("_id") @db.ObjectId 25 | name String 26 | description String? 27 | image String? 28 | quizzes Quiz[] 29 | categoryStats CategoryStat[] // reseve relation to CategoryStat 30 | } 31 | 32 | model Quiz { 33 | id String @id @default(auto()) @map("_id") @db.ObjectId 34 | title String 35 | description String? 36 | image String? 37 | categoryId String @db.ObjectId 38 | category Category @relation(fields: [categoryId], references: [id]) 39 | questions Question[] 40 | } 41 | 42 | model Question{ 43 | id String @id @default(auto()) @map("_id") @db.ObjectId 44 | text String 45 | quizId String @db.ObjectId 46 | difficulty String? 47 | quiz Quiz @relation(fields: [quizId], references: [id], onDelete: Cascade) 48 | options Option[] 49 | } 50 | 51 | model Option{ 52 | id String @id @default(auto()) @map("_id") @db.ObjectId 53 | text String 54 | isCorrect Boolean 55 | questionId String @db.ObjectId 56 | question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) 57 | } 58 | 59 | 60 | model CategoryStat { 61 | id String @id @default(auto()) @map("_id") @db.ObjectId 62 | userId String @db.ObjectId 63 | user User @relation(fields: [userId], references: [id]) 64 | categoryId String @db.ObjectId 65 | category Category @relation(fields: [categoryId], references: [id]) 66 | attempts Int @default(0) // Total attempts in this category 67 | completed Int @default(0) // Total completed quizzes in this category 68 | averageScore Float? 69 | lastAttempt DateTime? // Last attempt in this category 70 | 71 | @@unique([userId, categoryId]) 72 | } -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | 9 | --foreground: 240 10% 3.9%; 10 | 11 | --card: 0 0% 100%; 12 | 13 | --card-foreground: 240 10% 3.9%; 14 | 15 | --popover: 0 0% 100%; 16 | 17 | --popover-foreground: 240 10% 3.9%; 18 | 19 | --primary: 240 5.9% 10%; 20 | 21 | --primary-foreground: 0 0% 98%; 22 | 23 | --secondary: 240 4.8% 95.9%; 24 | 25 | --secondary-foreground: 240 5.9% 10%; 26 | 27 | --muted: 240 4.8% 95.9%; 28 | 29 | --muted-foreground: 240 3.8% 46.1%; 30 | 31 | --accent: 240 4.8% 95.9%; 32 | 33 | --accent-foreground: 240 5.9% 10%; 34 | 35 | --destructive: 0 84.2% 60.2%; 36 | 37 | --destructive-foreground: 0 0% 98%; 38 | 39 | --border: 240 5.9% 90%; 40 | 41 | --input: 240 5.9% 90%; 42 | 43 | --ring: 240 10% 3.9%; 44 | 45 | --chart-1: 12 76% 61%; 46 | 47 | --chart-2: 173 58% 39%; 48 | 49 | --chart-3: 197 37% 24%; 50 | 51 | --chart-4: 43 74% 66%; 52 | 53 | --chart-5: 27 87% 67%; 54 | 55 | --radius: 0.5rem; 56 | 57 | --blue-400: #60a5fa; 58 | --green-400: #4ade80; 59 | } 60 | .dark { 61 | --background: 240 10% 3.9%; 62 | 63 | --foreground: 0 0% 98%; 64 | 65 | --card: 240 10% 3.9%; 66 | 67 | --card-foreground: 0 0% 98%; 68 | 69 | --popover: 240 10% 3.9%; 70 | 71 | --popover-foreground: 0 0% 98%; 72 | 73 | --primary: 0 0% 98%; 74 | 75 | --primary-foreground: 240 5.9% 10%; 76 | 77 | --secondary: 240 3.7% 15.9%; 78 | 79 | --secondary-foreground: 0 0% 98%; 80 | 81 | --muted: 240 3.7% 15.9%; 82 | 83 | --muted-foreground: 240 5% 64.9%; 84 | 85 | --accent: 240 3.7% 15.9%; 86 | 87 | --accent-foreground: 0 0% 98%; 88 | 89 | --destructive: 0 62.8% 30.6%; 90 | 91 | --destructive-foreground: 0 0% 98%; 92 | 93 | --border: 240 3.7% 15.9%; 94 | 95 | --input: 240 3.7% 15.9%; 96 | 97 | --ring: 240 4.9% 83.9%; 98 | 99 | --chart-1: 220 70% 50%; 100 | 101 | --chart-2: 160 60% 45%; 102 | 103 | --chart-3: 30 80% 55%; 104 | 105 | --chart-4: 280 65% 60%; 106 | 107 | --chart-5: 340 75% 55%; 108 | } 109 | } 110 | 111 | @layer base { 112 | * { 113 | @apply border-border; 114 | } 115 | body { 116 | @apply bg-background text-foreground; 117 | } 118 | } 119 | 120 | .loader { 121 | font-weight: bold; 122 | font-family: inherit; 123 | font-size: 5rem; 124 | animation: l1 1s linear infinite alternate; 125 | } 126 | .loader:before { 127 | content: "Loading..."; 128 | } 129 | @keyframes l1 { 130 | to { 131 | opacity: 0; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/categories/[categoryId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs/server"; 2 | import React from "react"; 3 | import prisma from "@/utils/connect"; 4 | import { IQuiz } from "@/types/types"; 5 | import QuizCard from "@/components/quiz/QuizCard"; 6 | import { request } from "@arcjet/next"; 7 | import { aj } from "@/lib/arcject"; 8 | import Countdown from "@/components/Countdown"; 9 | 10 | async function page({ params }: any) { 11 | const { categoryId } = await params; 12 | const { userId } = await auth(); 13 | const req = await request(); 14 | 15 | const decision = await aj.protect(req, { 16 | userId: userId ?? "", 17 | requested: 2, 18 | }); 19 | 20 | if (decision.isDenied()) { 21 | if (decision.reason.isRateLimit()) { 22 | const resetTime = decision.reason?.resetTime; 23 | 24 | if (!resetTime) { 25 | return ( 26 |
27 |

Rate limit exceeded

28 |
29 | ); 30 | } 31 | 32 | // calculate the time left on the server 33 | const currentTime = Date.now(); 34 | const resetTimestamp = new Date(resetTime).getTime(); 35 | const timeLeft = Math.max( 36 | Math.ceil((resetTimestamp - currentTime) / 1000), 37 | 0 38 | ); // convert to seconds 39 | 40 | return ( 41 |
42 |

43 | Too many requests :( 44 |

45 |

You have exceeded the rate limit for this request.

46 | 47 | 48 |
49 | ); 50 | } 51 | } 52 | 53 | if (!categoryId) { 54 | return null; 55 | } 56 | 57 | const quizzes = await prisma.quiz.findMany({ 58 | where: { categoryId }, 59 | include: { 60 | questions: { 61 | select: { 62 | id: true, 63 | text: true, 64 | difficulty: true, 65 | options: { 66 | select: { 67 | id: true, 68 | text: true, 69 | isCorrect: true, 70 | }, 71 | }, 72 | }, 73 | }, 74 | }, 75 | orderBy: { 76 | id: "asc", 77 | }, 78 | }); 79 | 80 | return ( 81 |
82 |

All Quizzes

83 | 84 | {quizzes.length > 0 ? ( 85 |
86 | {quizzes.map((quiz) => ( 87 | 88 | ))} 89 |
90 | ) : ( 91 |

92 | No quizzes found for this Category 93 |

94 | )} 95 |
96 | ); 97 | } 98 | 99 | export default page; 100 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { chart, home, login } from "@/utils/Icons"; 3 | import { SignedIn, SignedOut, UserButton } from "@clerk/nextjs"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | import { usePathname, useRouter } from "next/navigation"; 7 | import React from "react"; 8 | import { Button } from "./ui/button"; 9 | 10 | function Header() { 11 | const pathname = usePathname(); 12 | const router = useRouter(); 13 | 14 | const menu = [ 15 | { 16 | name: "Home", 17 | icon: home, 18 | link: "/", 19 | }, 20 | { 21 | name: "My Stats", 22 | icon: chart, 23 | link: "/stats", 24 | }, 25 | ]; 26 | 27 | return ( 28 |
29 | 85 |
86 | ); 87 | } 88 | 89 | export default Header; 90 | -------------------------------------------------------------------------------- /components/CategoryBarChart.tsx: -------------------------------------------------------------------------------- 1 | import { ICategoryStats } from "@/types/types"; 2 | import React from "react"; 3 | 4 | interface Props { 5 | categoryData: ICategoryStats; 6 | } 7 | 8 | import { TrendingUp } from "lucide-react"; 9 | import { Bar, BarChart, CartesianGrid, Rectangle, XAxis } from "recharts"; 10 | import { 11 | Card, 12 | CardContent, 13 | CardDescription, 14 | CardFooter, 15 | CardHeader, 16 | CardTitle, 17 | } from "@/components/ui/card"; 18 | 19 | import { 20 | ChartConfig, 21 | ChartContainer, 22 | ChartTooltip, 23 | ChartTooltipContent, 24 | } from "@/components/ui/chart"; 25 | import { formatTime } from "@/utils/formatTime"; 26 | 27 | function CategoryBarChart({ categoryData }: Props) { 28 | const chartData = [ 29 | { 30 | key: "attempts", 31 | value: categoryData.attempts, 32 | fill: "var(--blue-400)", 33 | }, 34 | { 35 | key: "completed", 36 | value: categoryData.completed, 37 | fill: "var(--green-400)", 38 | }, 39 | ]; 40 | 41 | const chartConfig = { 42 | attempts: { 43 | label: "Attempts", 44 | color: "hsl(var(--chart-1))", 45 | }, 46 | completed: { 47 | label: "Completed", 48 | color: "hsl(var(--chart-2))", 49 | }, 50 | } satisfies ChartConfig; 51 | 52 | return ( 53 | 54 | 55 | {categoryData.category?.name} 56 | Attempts vs Completions 57 | 58 | 59 | 60 | 61 | 67 | chartConfig[value as keyof typeof chartConfig]?.label 68 | } 69 | /> 70 | } 73 | /> 74 | { 79 | return ( 80 | 87 | ); 88 | }} 89 | /> 90 | 91 | 92 | 93 | 94 |
95 | Attempted on {formatTime(categoryData.lastAttempt)} 96 |
97 |
98 |
99 | ); 100 | } 101 | 102 | export default CategoryBarChart; 103 | -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
[role=checkbox]]:translate-y-[2px]", 77 | className 78 | )} 79 | {...props} 80 | /> 81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | )) 97 | TableCell.displayName = "TableCell" 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
108 | )) 109 | TableCaption.displayName = "TableCaption" 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | } 121 | -------------------------------------------------------------------------------- /data/csQuestions.js: -------------------------------------------------------------------------------- 1 | const csQuestions = [ 2 | { 3 | text: "What does CPU stand for?", 4 | options: [ 5 | { text: "Central Processing Unit", isCorrect: true }, 6 | { text: "Central Programming Unit", isCorrect: false }, 7 | { text: "Central Process Unit", isCorrect: false }, 8 | { text: "Computer Processing Unit", isCorrect: false }, 9 | ], 10 | }, 11 | { 12 | text: "What is the main purpose of an operating system?", 13 | options: [ 14 | { text: "To manage computer hardware and software", isCorrect: true }, 15 | { text: "To run antivirus software", isCorrect: false }, 16 | { text: "To process user data", isCorrect: false }, 17 | { text: "To perform arithmetic calculations", isCorrect: false }, 18 | ], 19 | }, 20 | { 21 | text: "What does HTML stand for?", 22 | options: [ 23 | { text: "HyperText Markup Language", isCorrect: true }, 24 | { text: "HyperText Machine Language", isCorrect: false }, 25 | { text: "HyperText Modern Language", isCorrect: false }, 26 | { text: "HyperTool Markup Language", isCorrect: false }, 27 | ], 28 | }, 29 | { 30 | text: "Which data structure uses LIFO (Last In, First Out)?", 31 | options: [ 32 | { text: "Stack", isCorrect: true }, 33 | { text: "Queue", isCorrect: false }, 34 | { text: "Array", isCorrect: false }, 35 | { text: "Tree", isCorrect: false }, 36 | ], 37 | }, 38 | { 39 | text: "What is a primary key in a database?", 40 | options: [ 41 | { text: "A unique identifier for a record", isCorrect: true }, 42 | { text: "A foreign key for table relationships", isCorrect: false }, 43 | { text: "A column to store large data", isCorrect: false }, 44 | { text: "A tool to perform queries", isCorrect: false }, 45 | ], 46 | }, 47 | { 48 | text: "Which programming language is primarily used for Android app development?", 49 | options: [ 50 | { text: "Java", isCorrect: true }, 51 | { text: "Python", isCorrect: false }, 52 | { text: "C#", isCorrect: false }, 53 | { text: "Ruby", isCorrect: false }, 54 | ], 55 | }, 56 | { 57 | text: "What is the binary representation of the decimal number 5?", 58 | options: [ 59 | { text: "101", isCorrect: true }, 60 | { text: "110", isCorrect: false }, 61 | { text: "111", isCorrect: false }, 62 | { text: "100", isCorrect: false }, 63 | ], 64 | }, 65 | { 66 | text: "Which sorting algorithm is the fastest in the average case?", 67 | options: [ 68 | { text: "Merge Sort", isCorrect: false }, 69 | { text: "Quick Sort", isCorrect: true }, 70 | { text: "Bubble Sort", isCorrect: false }, 71 | { text: "Selection Sort", isCorrect: false }, 72 | ], 73 | }, 74 | { 75 | text: "What is the purpose of an IP address?", 76 | options: [ 77 | { text: "To uniquely identify a device on a network", isCorrect: true }, 78 | { text: "To store user passwords", isCorrect: false }, 79 | { text: "To encrypt network traffic", isCorrect: false }, 80 | { text: "To assign memory to a program", isCorrect: false }, 81 | ], 82 | }, 83 | { 84 | text: "Which of the following is a NoSQL database?", 85 | options: [ 86 | { text: "MongoDB", isCorrect: true }, 87 | { text: "MySQL", isCorrect: false }, 88 | { text: "PostgreSQL", isCorrect: false }, 89 | { text: "SQLite", isCorrect: false }, 90 | ], 91 | }, 92 | ]; 93 | 94 | module.exports = csQuestions; 95 | -------------------------------------------------------------------------------- /data/chemistryQuestions.js: -------------------------------------------------------------------------------- 1 | const chemistryQuestions = [ 2 | { 3 | text: "What is the most abundant gas in Earth's atmosphere?", 4 | options: [ 5 | { text: "Nitrogen", isCorrect: true }, 6 | { text: "Oxygen", isCorrect: false }, 7 | { text: "Carbon Dioxide", isCorrect: false }, 8 | { text: "Argon", isCorrect: false }, 9 | ], 10 | }, 11 | { 12 | text: "What is the chemical formula for water?", 13 | options: [ 14 | { text: "H2O", isCorrect: true }, 15 | { text: "O2", isCorrect: false }, 16 | { text: "CO2", isCorrect: false }, 17 | { text: "H2O2", isCorrect: false }, 18 | ], 19 | }, 20 | { 21 | text: "What is the pH of a neutral solution?", 22 | options: [ 23 | { text: "7", isCorrect: true }, 24 | { text: "0", isCorrect: false }, 25 | { text: "14", isCorrect: false }, 26 | { text: "1", isCorrect: false }, 27 | ], 28 | }, 29 | { 30 | text: "What is the molar mass of carbon dioxide (CO2)?", 31 | options: [ 32 | { text: "44 g/mol", isCorrect: true }, 33 | { text: "28 g/mol", isCorrect: false }, 34 | { text: "18 g/mol", isCorrect: false }, 35 | { text: "32 g/mol", isCorrect: false }, 36 | ], 37 | }, 38 | { 39 | text: "Which element is represented by the symbol 'Na'?", 40 | options: [ 41 | { text: "Sodium", isCorrect: true }, 42 | { text: "Nitrogen", isCorrect: false }, 43 | { text: "Neon", isCorrect: false }, 44 | { text: "Nickel", isCorrect: false }, 45 | ], 46 | }, 47 | { 48 | text: "What type of bond is formed between two water molecules?", 49 | options: [ 50 | { text: "Hydrogen bond", isCorrect: true }, 51 | { text: "Ionic bond", isCorrect: false }, 52 | { text: "Covalent bond", isCorrect: false }, 53 | { text: "Metallic bond", isCorrect: false }, 54 | ], 55 | }, 56 | { 57 | text: "What is the atomic number of oxygen?", 58 | options: [ 59 | { text: "8", isCorrect: true }, 60 | { text: "6", isCorrect: false }, 61 | { text: "7", isCorrect: false }, 62 | { text: "9", isCorrect: false }, 63 | ], 64 | }, 65 | { 66 | text: "Which gas is commonly used in balloons because it is lighter than air?", 67 | options: [ 68 | { text: "Helium", isCorrect: true }, 69 | { text: "Hydrogen", isCorrect: false }, 70 | { text: "Nitrogen", isCorrect: false }, 71 | { text: "Oxygen", isCorrect: false }, 72 | ], 73 | }, 74 | { 75 | text: "What is the main component of natural gas?", 76 | options: [ 77 | { text: "Methane", isCorrect: true }, 78 | { text: "Propane", isCorrect: false }, 79 | { text: "Butane", isCorrect: false }, 80 | { text: "Ethane", isCorrect: false }, 81 | ], 82 | }, 83 | { 84 | text: "Which acid is found in vinegar?", 85 | options: [ 86 | { text: "Acetic acid", isCorrect: true }, 87 | { text: "Citric acid", isCorrect: false }, 88 | { text: "Lactic acid", isCorrect: false }, 89 | { text: "Sulfuric acid", isCorrect: false }, 90 | ], 91 | }, 92 | { 93 | text: "What is the process of converting a liquid to a gas called?", 94 | options: [ 95 | { text: "Evaporation", isCorrect: true }, 96 | { text: "Condensation", isCorrect: false }, 97 | { text: "Sublimation", isCorrect: false }, 98 | { text: "Freezing", isCorrect: false }, 99 | ], 100 | }, 101 | { 102 | text: "Which of the following is an alkali metal?", 103 | options: [ 104 | { text: "Potassium", isCorrect: true }, 105 | { text: "Calcium", isCorrect: false }, 106 | { text: "Iron", isCorrect: false }, 107 | { text: "Aluminum", isCorrect: false }, 108 | ], 109 | }, 110 | { 111 | text: "What is the name of the process in which plants produce glucose?", 112 | options: [ 113 | { text: "Photosynthesis", isCorrect: true }, 114 | { text: "Respiration", isCorrect: false }, 115 | { text: "Fermentation", isCorrect: false }, 116 | { text: "Oxidation", isCorrect: false }, 117 | ], 118 | }, 119 | { 120 | text: "Which compound is known as the universal solvent?", 121 | options: [ 122 | { text: "Water", isCorrect: true }, 123 | { text: "Ethanol", isCorrect: false }, 124 | { text: "Acetone", isCorrect: false }, 125 | { text: "Methanol", isCorrect: false }, 126 | ], 127 | }, 128 | { 129 | text: "What is the chemical formula for table salt?", 130 | options: [ 131 | { text: "NaCl", isCorrect: true }, 132 | { text: "KCl", isCorrect: false }, 133 | { text: "CaCl2", isCorrect: false }, 134 | { text: "MgCl2", isCorrect: false }, 135 | ], 136 | }, 137 | ]; 138 | 139 | module.exports = chemistryQuestions; 140 | -------------------------------------------------------------------------------- /data/physicsQuestions.js: -------------------------------------------------------------------------------- 1 | const physicsQuestions = [ 2 | { 3 | text: "What is Newton's second law of motion?", 4 | options: [ 5 | { text: "Force equals mass times acceleration", isCorrect: true }, 6 | { text: "An object in motion stays in motion", isCorrect: false }, 7 | { 8 | text: "For every action, there is an equal and opposite reaction", 9 | isCorrect: false, 10 | }, 11 | { text: "Energy cannot be created or destroyed", isCorrect: false }, 12 | ], 13 | }, 14 | { 15 | text: "What is the SI unit of force?", 16 | options: [ 17 | { text: "Newton", isCorrect: true }, 18 | { text: "Joule", isCorrect: false }, 19 | { text: "Pascal", isCorrect: false }, 20 | { text: "Watt", isCorrect: false }, 21 | ], 22 | }, 23 | { 24 | text: "What does E=mc^2 represent?", 25 | options: [ 26 | { text: "Energy-mass equivalence", isCorrect: true }, 27 | { text: "Kinetic energy formula", isCorrect: false }, 28 | { text: "Force-mass relationship", isCorrect: false }, 29 | { text: "Work-energy theorem", isCorrect: false }, 30 | ], 31 | }, 32 | { 33 | text: "What is the universal gravitational constant (G)?", 34 | options: [ 35 | { text: "6.674 × 10^-11 N·m²/kg²", isCorrect: true }, 36 | { text: "9.8 m/s²", isCorrect: false }, 37 | { text: "1.6 × 10^-19 C", isCorrect: false }, 38 | { text: "3.0 × 10^8 m/s", isCorrect: false }, 39 | ], 40 | }, 41 | { 42 | text: "What is the formula for kinetic energy?", 43 | options: [ 44 | { text: "1/2 mv²", isCorrect: true }, 45 | { text: "mgh", isCorrect: false }, 46 | { text: "mv", isCorrect: false }, 47 | { text: "qV", isCorrect: false }, 48 | ], 49 | }, 50 | { 51 | text: "What is the acceleration due to gravity on Earth?", 52 | options: [ 53 | { text: "9.8 m/s²", isCorrect: true }, 54 | { text: "6.674 × 10^-11 m/s²", isCorrect: false }, 55 | { text: "3.0 × 10^8 m/s²", isCorrect: false }, 56 | { text: "1.6 × 10^-19 m/s²", isCorrect: false }, 57 | ], 58 | }, 59 | { 60 | text: "Which law states that the pressure of a gas is inversely proportional to its volume?", 61 | options: [ 62 | { text: "Boyle's Law", isCorrect: true }, 63 | { text: "Charles's Law", isCorrect: false }, 64 | { text: "Avogadro's Law", isCorrect: false }, 65 | { text: "Pascal's Law", isCorrect: false }, 66 | ], 67 | }, 68 | { 69 | text: "What is the SI unit of power?", 70 | options: [ 71 | { text: "Watt", isCorrect: true }, 72 | { text: "Joule", isCorrect: false }, 73 | { text: "Newton", isCorrect: false }, 74 | { text: "Ampere", isCorrect: false }, 75 | ], 76 | }, 77 | { 78 | text: "What is the work done when a 10 N force moves an object 5 m in the direction of the force?", 79 | options: [ 80 | { text: "50 J", isCorrect: true }, 81 | { text: "5 J", isCorrect: false }, 82 | { text: "10 J", isCorrect: false }, 83 | { text: "500 J", isCorrect: false }, 84 | ], 85 | }, 86 | { 87 | text: "What is the speed of light in a vacuum?", 88 | options: [ 89 | { text: "3.0 × 10^8 m/s", isCorrect: true }, 90 | { text: "9.8 m/s", isCorrect: false }, 91 | { text: "1.6 × 10^-19 m/s", isCorrect: false }, 92 | { text: "6.674 × 10^-11 m/s", isCorrect: false }, 93 | ], 94 | }, 95 | { 96 | text: "What type of wave is sound?", 97 | options: [ 98 | { text: "Longitudinal", isCorrect: true }, 99 | { text: "Transverse", isCorrect: false }, 100 | { text: "Electromagnetic", isCorrect: false }, 101 | { text: "Stationary", isCorrect: false }, 102 | ], 103 | }, 104 | { 105 | text: "What is the formula for Ohm's Law?", 106 | options: [ 107 | { text: "V = IR", isCorrect: true }, 108 | { text: "P = VI", isCorrect: false }, 109 | { text: "I = V/R", isCorrect: false }, 110 | { text: "R = V/I", isCorrect: false }, 111 | ], 112 | }, 113 | { 114 | text: "What is the SI unit of electric charge?", 115 | options: [ 116 | { text: "Coulomb", isCorrect: true }, 117 | { text: "Ampere", isCorrect: false }, 118 | { text: "Volt", isCorrect: false }, 119 | { text: "Ohm", isCorrect: false }, 120 | ], 121 | }, 122 | { 123 | text: "What is the energy of a photon with a frequency of 5 × 10^14 Hz? (h = 6.63 × 10^-34 J·s)", 124 | options: [ 125 | { text: "3.315 × 10^-19 J", isCorrect: true }, 126 | { text: "6.63 × 10^-19 J", isCorrect: false }, 127 | { text: "1.327 × 10^-18 J", isCorrect: false }, 128 | { text: "5.0 × 10^-14 J", isCorrect: false }, 129 | ], 130 | }, 131 | { 132 | text: "What is the period of a wave with a frequency of 10 Hz?", 133 | options: [ 134 | { text: "0.1 s", isCorrect: true }, 135 | { text: "10 s", isCorrect: false }, 136 | { text: "0.01 s", isCorrect: false }, 137 | { text: "1 s", isCorrect: false }, 138 | ], 139 | }, 140 | ]; 141 | 142 | module.exports = physicsQuestions; 143 | -------------------------------------------------------------------------------- /data/biologyQuestions.js: -------------------------------------------------------------------------------- 1 | const biologyQuestions = [ 2 | { 3 | text: "What is the primary function of mitochondria in cells?", 4 | options: [ 5 | { text: "Energy production", isCorrect: true }, 6 | { text: "Protein synthesis", isCorrect: false }, 7 | { text: "Photosynthesis", isCorrect: false }, 8 | { text: "Cell division", isCorrect: false }, 9 | ], 10 | }, 11 | { 12 | text: "What is the monomer unit of proteins?", 13 | options: [ 14 | { text: "Amino acids", isCorrect: true }, 15 | { text: "Nucleotides", isCorrect: false }, 16 | { text: "Monosaccharides", isCorrect: false }, 17 | { text: "Fatty acids", isCorrect: false }, 18 | ], 19 | }, 20 | { 21 | text: "Which organ is responsible for filtering blood in humans?", 22 | options: [ 23 | { text: "Kidney", isCorrect: true }, 24 | { text: "Liver", isCorrect: false }, 25 | { text: "Heart", isCorrect: false }, 26 | { text: "Lungs", isCorrect: false }, 27 | ], 28 | }, 29 | { 30 | text: "What is the primary pigment involved in photosynthesis?", 31 | options: [ 32 | { text: "Chlorophyll", isCorrect: true }, 33 | { text: "Carotene", isCorrect: false }, 34 | { text: "Xanthophyll", isCorrect: false }, 35 | { text: "Anthocyanin", isCorrect: false }, 36 | ], 37 | }, 38 | { 39 | text: "What is the process by which cells divide to form two identical daughter cells?", 40 | options: [ 41 | { text: "Mitosis", isCorrect: true }, 42 | { text: "Meiosis", isCorrect: false }, 43 | { text: "Binary fission", isCorrect: false }, 44 | { text: "Cytokinesis", isCorrect: false }, 45 | ], 46 | }, 47 | { 48 | text: "What is the molecule that carries genetic information in most living organisms?", 49 | options: [ 50 | { text: "DNA", isCorrect: true }, 51 | { text: "RNA", isCorrect: false }, 52 | { text: "Proteins", isCorrect: false }, 53 | { text: "Lipids", isCorrect: false }, 54 | ], 55 | }, 56 | { 57 | text: "What is the name of the process by which plants lose water through their leaves?", 58 | options: [ 59 | { text: "Transpiration", isCorrect: true }, 60 | { text: "Respiration", isCorrect: false }, 61 | { text: "Photosynthesis", isCorrect: false }, 62 | { text: "Excretion", isCorrect: false }, 63 | ], 64 | }, 65 | { 66 | text: "Which part of the brain controls coordination and balance?", 67 | options: [ 68 | { text: "Cerebellum", isCorrect: true }, 69 | { text: "Cerebrum", isCorrect: false }, 70 | { text: "Medulla oblongata", isCorrect: false }, 71 | { text: "Hypothalamus", isCorrect: false }, 72 | ], 73 | }, 74 | { 75 | text: "What is the structural and functional unit of the kidney?", 76 | options: [ 77 | { text: "Nephron", isCorrect: true }, 78 | { text: "Neuron", isCorrect: false }, 79 | { text: "Alveolus", isCorrect: false }, 80 | { text: "Capillary", isCorrect: false }, 81 | ], 82 | }, 83 | { 84 | text: "Which organ in the human body produces insulin?", 85 | options: [ 86 | { text: "Pancreas", isCorrect: true }, 87 | { text: "Liver", isCorrect: false }, 88 | { text: "Kidney", isCorrect: false }, 89 | { text: "Stomach", isCorrect: false }, 90 | ], 91 | }, 92 | { 93 | text: "What is the name of the bond that joins amino acids together?", 94 | options: [ 95 | { text: "Peptide bond", isCorrect: true }, 96 | { text: "Ionic bond", isCorrect: false }, 97 | { text: "Hydrogen bond", isCorrect: false }, 98 | { text: "Glycosidic bond", isCorrect: false }, 99 | ], 100 | }, 101 | { 102 | text: "What is the term for organisms that produce their own food?", 103 | options: [ 104 | { text: "Autotrophs", isCorrect: true }, 105 | { text: "Heterotrophs", isCorrect: false }, 106 | { text: "Parasites", isCorrect: false }, 107 | { text: "Decomposers", isCorrect: false }, 108 | ], 109 | }, 110 | { 111 | text: "Which type of blood cell is primarily responsible for immunity?", 112 | options: [ 113 | { text: "White blood cells", isCorrect: true }, 114 | { text: "Red blood cells", isCorrect: false }, 115 | { text: "Platelets", isCorrect: false }, 116 | { text: "Plasma", isCorrect: false }, 117 | ], 118 | }, 119 | { 120 | text: "What is the function of ribosomes in a cell?", 121 | options: [ 122 | { text: "Protein synthesis", isCorrect: true }, 123 | { text: "Lipid storage", isCorrect: false }, 124 | { text: "Energy production", isCorrect: false }, 125 | { text: "DNA replication", isCorrect: false }, 126 | ], 127 | }, 128 | { 129 | text: "What is the basic structural unit of all living organisms?", 130 | options: [ 131 | { text: "Cell", isCorrect: true }, 132 | { text: "Tissue", isCorrect: false }, 133 | { text: "Organ", isCorrect: false }, 134 | { text: "Molecule", isCorrect: false }, 135 | ], 136 | }, 137 | ]; 138 | 139 | module.exports = biologyQuestions; 140 | -------------------------------------------------------------------------------- /components/UserStats.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useUser } from "@clerk/nextjs"; 3 | import React from "react"; 4 | import Loader from "./Loader"; 5 | import Image from "next/image"; 6 | import { formatTime } from "@/utils/formatTime"; 7 | import { checkAbc, crosshairs } from "@/utils/Icons"; 8 | import CategoryBarChart from "./CategoryBarChart"; 9 | import { 10 | Table, 11 | TableBody, 12 | TableCell, 13 | TableHead, 14 | TableHeader, 15 | TableRow, 16 | } from "./ui/table"; 17 | import { ICategoryStats } from "@/types/types"; 18 | 19 | function UserStats({ userStats }: any) { 20 | const { user, isLoaded } = useUser(); 21 | 22 | if (!isLoaded) { 23 | return ; 24 | } 25 | 26 | // get the most recent attempt date 27 | const recentAttemptDate = userStats?.categoryStats.reduce( 28 | (acc: any, curr: any) => { 29 | const currentDate = new Date(curr.lastAttempt); 30 | return currentDate > acc ? currentDate : acc; 31 | }, 32 | new Date(0) 33 | ); 34 | 35 | const totalAttempts = userStats?.categoryStats.reduce( 36 | (acc: number, curr: any) => acc + curr.attempts, 37 | 0 38 | ); 39 | 40 | const totalCompleted = userStats?.categoryStats.reduce( 41 | (acc: number, curr: any) => acc + curr.completed, 42 | 0 43 | ); 44 | // show the 2 most recent attempts 45 | const latestStats = userStats?.categoryStats 46 | .slice(-2) 47 | .sort((a: any, b: any) => { 48 | return ( 49 | new Date(b.lastAttempt).getTime() - new Date(a.lastAttempt).getTime() 50 | ); 51 | }); 52 | 53 | console.log("User stats:", userStats.categoryStats); 54 | 55 | return ( 56 |
57 |
58 | Profile Image 65 |
66 | 67 |
68 |

Overview

69 |

70 | A summary of your recent activity and performance 71 |

72 |
73 | 74 |
75 |
76 |

{user?.firstName}

77 |

Recent Attempt

78 |

79 | {formatTime(recentAttemptDate)} 80 |

81 |
82 | 83 |
84 |
{crosshairs}
85 |
86 |

Total Attempts

87 |

{totalAttempts}

88 |
89 |
90 | 91 |
92 |
{checkAbc}
93 |
94 |

Total Completed

95 |

{totalCompleted}

96 |
97 |
98 |
99 | 100 |
101 | {latestStats?.map((category: any) => ( 102 | 103 | ))} 104 |
105 | 106 |
107 |

Detailed Category Stats

108 |

109 | Breakdown of performance by category 110 |

111 |
112 | 113 |
114 | 115 | 116 | 117 | Category 118 | Attempts 119 | Completed 120 | Average Score 121 | Last Attempt 122 | 123 | 124 | 125 | {userStats?.categoryStats.map((category: ICategoryStats) => ( 126 | 127 | 128 | {category.category.name} 129 | 130 | {category.attempts} 131 | {category.completed} 132 | 133 | {category.averageScore !== null 134 | ? category.averageScore.toFixed(2) 135 | : "N/A"} 136 | 137 | {formatTime(category.lastAttempt)} 138 | 139 | ))} 140 | 141 |
142 |
143 |
144 | ); 145 | } 146 | 147 | export default UserStats; 148 | -------------------------------------------------------------------------------- /app/quiz/setup/[quizId]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import { Input } from "@/components/ui/input"; 4 | import { Label } from "@/components/ui/label"; 5 | import { 6 | Select, 7 | SelectContent, 8 | SelectItem, 9 | SelectTrigger, 10 | SelectValue, 11 | } from "@/components/ui/select"; 12 | import { useGlobalContext } from "@/context/globalContext"; 13 | import { play } from "@/utils/Icons"; 14 | import axios from "axios"; 15 | import { useRouter } from "next/navigation"; 16 | import React, { useEffect } from "react"; 17 | import toast from "react-hot-toast"; 18 | 19 | function page() { 20 | const router = useRouter(); 21 | const { quizSetup, setQuizSetup, selectedQuiz, setFilteredQuestions } = 22 | useGlobalContext(); 23 | 24 | useEffect(() => { 25 | if (!selectedQuiz) { 26 | router.push("/"); 27 | } 28 | }, [selectedQuiz, router]); 29 | 30 | useEffect(() => { 31 | const filteredQuestions = selectedQuiz?.questions.filter( 32 | (q: { difficulty: string }) => { 33 | return ( 34 | !quizSetup?.difficulty || 35 | quizSetup?.difficulty === "unspecified" || 36 | q?.difficulty.toLowerCase() === quizSetup?.difficulty.toLowerCase() 37 | ); 38 | } 39 | ); 40 | 41 | setFilteredQuestions(filteredQuestions); 42 | }, [quizSetup]); 43 | 44 | const handleQuestionChange = (e: React.ChangeEvent) => { 45 | const value = parseInt(e.target.value, 10); 46 | const maxQuestions = selectedQuiz?.questions.length || 1; 47 | 48 | const newCount = 49 | isNaN(value) || value < 1 ? 1 : Math.min(value, maxQuestions); 50 | 51 | setQuizSetup((prev: {}) => ({ ...prev, questionCount: newCount })); 52 | }; 53 | 54 | const handleDifficultyChange = (difficulty: string) => { 55 | setQuizSetup((prev: {}) => ({ ...prev, difficulty })); 56 | 57 | console.log("Difficulty: ", difficulty); 58 | }; 59 | 60 | const startQuiz = async () => { 61 | const selectedQuestions = selectedQuiz?.questions 62 | .slice(0, quizSetup?.questionCount) 63 | .filter((q: { difficulty: string }) => { 64 | return ( 65 | quizSetup?.difficulty || 66 | q.difficulty?.toLowerCase() === 67 | selectedQuiz?.difficulty?.toLowerCase() 68 | ); 69 | }); 70 | 71 | if (selectedQuestions.length > 0) { 72 | //update the db for quiz attempt start 73 | 74 | try { 75 | await axios.post("/api/user/quiz/start", { 76 | categoryId: selectedQuiz?.categoryId, 77 | quizId: selectedQuiz?.id, 78 | }); 79 | } catch (error) { 80 | console.log("Error starting quiz: ", error); 81 | } 82 | 83 | // pushh to the quiz page 84 | router.push("/quiz"); 85 | } else { 86 | toast.error("No questions found for the selected criteria"); 87 | } 88 | }; 89 | 90 | return ( 91 |
92 |
93 |

Quiz Setup

94 | 95 |
96 |
97 | 100 | 108 |
109 | 110 |
111 | 114 | 115 | 126 |
127 | 128 |
129 | 132 | 133 | 147 |
148 |
149 |
150 | 151 |
152 | 159 |
160 |
161 | ); 162 | } 163 | 164 | export default page; 165 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )) 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )) 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )) 100 | SelectContent.displayName = SelectPrimitive.Content.displayName 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )) 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | {children} 132 | 133 | )) 134 | SelectItem.displayName = SelectPrimitive.Item.displayName 135 | 136 | const SelectSeparator = React.forwardRef< 137 | React.ElementRef, 138 | React.ComponentPropsWithoutRef 139 | >(({ className, ...props }, ref) => ( 140 | 145 | )) 146 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 147 | 148 | export { 149 | Select, 150 | SelectGroup, 151 | SelectValue, 152 | SelectTrigger, 153 | SelectContent, 154 | SelectLabel, 155 | SelectItem, 156 | SelectSeparator, 157 | SelectScrollUpButton, 158 | SelectScrollDownButton, 159 | } 160 | -------------------------------------------------------------------------------- /app/quiz/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import { useGlobalContext } from "@/context/globalContext"; 4 | import { IOption, IQuestion, IResponse } from "@/types/types"; 5 | import { flag, next } from "@/utils/Icons"; 6 | import axios from "axios"; 7 | import { useRouter } from "next/navigation"; 8 | import React, { useEffect } from "react"; 9 | import toast from "react-hot-toast"; 10 | 11 | function page() { 12 | const { 13 | selectedQuiz, 14 | quizSetup, 15 | setQuizSetup, 16 | setQuizResponses, 17 | filteredQuestions, 18 | } = useGlobalContext(); 19 | const router = useRouter(); 20 | 21 | const [currentIndex, setCurrentIndex] = React.useState(0); 22 | const [activeQuestion, setActiveQuestion] = React.useState(null) as any; 23 | const [responses, setResponses] = React.useState([]); 24 | const [shuffledOptions, setShuffledOptions] = React.useState([]); 25 | const [shuffledQuestions, setShuffledQuestions] = React.useState( 26 | [] 27 | ); 28 | 29 | if (!selectedQuiz) { 30 | router.push("/"); 31 | return null; 32 | } 33 | 34 | // shuffle questions when the quiz is started 35 | useEffect(() => { 36 | const allQuestions = filteredQuestions.slice(0, quizSetup?.questionCount); 37 | 38 | setShuffledQuestions(shuffleArray([...allQuestions])); 39 | }, [selectedQuiz, quizSetup]); 40 | 41 | // suffle options when the active question changes 42 | useEffect(() => { 43 | if (shuffledQuestions[currentIndex]) { 44 | // shuffle options for the current question 45 | setShuffledOptions( 46 | shuffleArray([...shuffledQuestions[currentIndex].options]) 47 | ); 48 | } 49 | }, [shuffledQuestions, currentIndex]); 50 | 51 | // Fisher-Yates Shuffle Algorithm 52 | const shuffleArray = (array: any[]) => { 53 | for (let i = array.length - 1; i > 0; --i) { 54 | // generate a random index between 0 and i 55 | const j = Math.floor(Math.random() * (i + 1)); 56 | 57 | // swap elements --> destructuring assignment 58 | [array[i], array[j]] = [array[j], array[i]]; 59 | } 60 | 61 | return array; 62 | }; 63 | 64 | const handleActiveQuestion = (option: any) => { 65 | if (!shuffledQuestions[currentIndex]) return; 66 | 67 | const response = { 68 | questionId: shuffledQuestions[currentIndex].id, 69 | optionId: option.id, 70 | isCorrect: option.isCorrect, 71 | }; 72 | 73 | setResponses((prev) => { 74 | // check if the response already exists 75 | const existingIndex = prev.findIndex((res) => { 76 | return res.questionId === response.questionId; 77 | }); 78 | 79 | // update the response if it exists 80 | 81 | if (existingIndex !== -1) { 82 | // update the response 83 | const updatedResponses = [...prev]; 84 | updatedResponses[existingIndex] = response; 85 | 86 | return updatedResponses; 87 | } else { 88 | return [...prev, response]; 89 | } 90 | }); 91 | 92 | // set the active question 93 | setActiveQuestion(option); 94 | }; 95 | 96 | const handleNextQuestion = () => { 97 | if (currentIndex < shuffledQuestions.length - 1) { 98 | setCurrentIndex((prev) => prev + 1); 99 | 100 | // reset the active question 101 | setActiveQuestion(null); 102 | } else { 103 | router.push("/quiz/results"); 104 | } 105 | }; 106 | 107 | const handleFinishQuiz = async () => { 108 | setQuizResponses(responses); 109 | 110 | const score = responses.filter((res) => res.isCorrect).length; 111 | 112 | try { 113 | const res = await axios.post("/api/user/quiz/finish", { 114 | categoryId: selectedQuiz.categoryId, 115 | quizId: selectedQuiz.id, 116 | score, 117 | responses, 118 | }); 119 | 120 | console.log("Quiz finished:", res.data); 121 | } catch (error) { 122 | console.log("Error finishing quiz:", error); 123 | } 124 | 125 | setQuizSetup({ 126 | questionCount: 1, 127 | category: null, 128 | difficulty: null, 129 | }); 130 | 131 | router.push("/results"); 132 | }; 133 | 134 | return ( 135 |
136 | {shuffledQuestions[currentIndex] ? ( 137 |
138 |
139 |

140 | Question: {currentIndex + 1} /{" "} 141 | {shuffledQuestions.length} 142 |

143 |

144 | {shuffledQuestions[currentIndex].text} 145 |

146 |
147 | 148 |
149 | {shuffledOptions.map((option, index) => ( 150 | 164 | ))} 165 |
166 |
167 | ) : ( 168 |

No questions found for this quiz

169 | )} 170 | 171 |
172 | 201 |
202 |
203 | ); 204 | } 205 | 206 | export default page; 207 | -------------------------------------------------------------------------------- /data/programmingQuestions.js: -------------------------------------------------------------------------------- 1 | const programmingQuestions = [ 2 | { 3 | text: "What does the acronym OOP stand for?", 4 | options: [ 5 | { text: "Object-Oriented Programming", isCorrect: true }, 6 | { text: "Objective-Oriented Programming", isCorrect: false }, 7 | { text: "Operation-Oriented Programming", isCorrect: false }, 8 | { text: "Object-Oriented Process", isCorrect: false }, 9 | ], 10 | difficulty: "Easy", 11 | }, 12 | { 13 | text: "Which language is primarily used for web development?", 14 | options: [ 15 | { text: "JavaScript", isCorrect: true }, 16 | { text: "Python", isCorrect: false }, 17 | { text: "C++", isCorrect: false }, 18 | { text: "Java", isCorrect: false }, 19 | ], 20 | difficulty: "Easy", 21 | }, 22 | { 23 | text: "What is the output of `console.log(typeof null)` in JavaScript?", 24 | options: [ 25 | { text: "object", isCorrect: true }, 26 | { text: "null", isCorrect: false }, 27 | { text: "undefined", isCorrect: false }, 28 | { text: "string", isCorrect: false }, 29 | ], 30 | difficulty: "Medium", 31 | }, 32 | { 33 | text: "Which method is used to add an element to the end of an array in JavaScript?", 34 | options: [ 35 | { text: "push()", isCorrect: true }, 36 | { text: "pop()", isCorrect: false }, 37 | { text: "shift()", isCorrect: false }, 38 | { text: "unshift()", isCorrect: false }, 39 | ], 40 | difficulty: "Easy", 41 | }, 42 | { 43 | text: "What is the primary purpose of the `virtual` keyword in C++?", 44 | options: [ 45 | { text: "To enable polymorphism", isCorrect: true }, 46 | { text: "To declare a global variable", isCorrect: false }, 47 | { text: "To optimize memory", isCorrect: false }, 48 | { text: "To create an abstract class", isCorrect: false }, 49 | ], 50 | difficulty: "Medium", 51 | }, 52 | { 53 | text: "What is the time complexity of binary search?", 54 | options: [ 55 | { text: "O(log n)", isCorrect: true }, 56 | { text: "O(n)", isCorrect: false }, 57 | { text: "O(1)", isCorrect: false }, 58 | { text: "O(n log n)", isCorrect: false }, 59 | ], 60 | difficulty: "Medium", 61 | }, 62 | { 63 | text: "Which programming paradigm focuses on functions and immutability?", 64 | options: [ 65 | { text: "Functional Programming", isCorrect: true }, 66 | { text: "Object-Oriented Programming", isCorrect: false }, 67 | { text: "Procedural Programming", isCorrect: false }, 68 | { text: "Logic Programming", isCorrect: false }, 69 | ], 70 | difficulty: "Medium", 71 | }, 72 | { 73 | text: "Which of the following is a valid way to declare a variable in Python?", 74 | options: [ 75 | { text: "variable_name = 10", isCorrect: true }, 76 | { text: "int variable_name = 10;", isCorrect: false }, 77 | { text: "let variable_name = 10;", isCorrect: false }, 78 | { text: "var variable_name = 10;", isCorrect: false }, 79 | ], 80 | difficulty: "Easy", 81 | }, 82 | { 83 | text: "What does the `finally` block do in a try-catch-finally structure?", 84 | options: [ 85 | { text: "Executes code regardless of an exception", isCorrect: true }, 86 | { text: "Handles the exception", isCorrect: false }, 87 | { text: "Rethrows the exception", isCorrect: false }, 88 | { text: "Prevents exceptions from propagating", isCorrect: false }, 89 | ], 90 | difficulty: "Medium", 91 | }, 92 | { 93 | text: "Which of the following is NOT a primitive data type in Java?", 94 | options: [ 95 | { text: "String", isCorrect: true }, 96 | { text: "int", isCorrect: false }, 97 | { text: "boolean", isCorrect: false }, 98 | { text: "float", isCorrect: false }, 99 | ], 100 | difficulty: "Medium", 101 | }, 102 | { 103 | text: "Which keyword in JavaScript is used to declare a constant variable?", 104 | options: [ 105 | { text: "const", isCorrect: true }, 106 | { text: "var", isCorrect: false }, 107 | { text: "let", isCorrect: false }, 108 | { text: "constant", isCorrect: false }, 109 | ], 110 | difficulty: "Easy", 111 | }, 112 | { 113 | text: "What is the default value of a reference type in Java?", 114 | options: [ 115 | { text: "null", isCorrect: true }, 116 | { text: "undefined", isCorrect: false }, 117 | { text: "0", isCorrect: false }, 118 | { text: "empty string", isCorrect: false }, 119 | ], 120 | difficulty: "Medium", 121 | }, 122 | { 123 | text: "Which programming language is known for its simplicity and readability?", 124 | options: [ 125 | { text: "Python", isCorrect: true }, 126 | { text: "C++", isCorrect: false }, 127 | { text: "Assembly", isCorrect: false }, 128 | { text: "Perl", isCorrect: false }, 129 | ], 130 | difficulty: "Easy", 131 | }, 132 | { 133 | text: "What does the `super` keyword do in Java?", 134 | options: [ 135 | { text: "Accesses the parent class constructor", isCorrect: true }, 136 | { text: "Creates a new instance", isCorrect: false }, 137 | { text: "References a subclass", isCorrect: false }, 138 | { text: "Accesses a static method", isCorrect: false }, 139 | ], 140 | difficulty: "Hard", 141 | }, 142 | { 143 | text: "What does the term 'closure' refer to in JavaScript?", 144 | options: [ 145 | { 146 | text: "A function retaining access to its lexical scope", 147 | isCorrect: true, 148 | }, 149 | { text: "A function that is immediately invoked", isCorrect: false }, 150 | { text: "A function without a return statement", isCorrect: false }, 151 | { text: "A function with no parameters", isCorrect: false }, 152 | ], 153 | difficulty: "Hard", 154 | }, 155 | { 156 | text: "Which of these is a valid way to create a new thread in Java?", 157 | options: [ 158 | { text: "Extend Thread class", isCorrect: true }, 159 | { text: "Implement Runnable", isCorrect: true }, 160 | { text: "Call thread()", isCorrect: false }, 161 | { text: "Use the ThreadFactory", isCorrect: true }, 162 | ], 163 | difficulty: "Hard", 164 | }, 165 | { 166 | text: "What is the difference between `==` and `===` in JavaScript?", 167 | options: [ 168 | { text: "`===` checks both value and type", isCorrect: true }, 169 | { text: "`==` checks only value", isCorrect: true }, 170 | { text: "`==` and `===` are identical", isCorrect: false }, 171 | { text: "`===` checks only type", isCorrect: false }, 172 | ], 173 | difficulty: "Medium", 174 | }, 175 | { 176 | text: "Which of the following is an immutable object in Java?", 177 | options: [ 178 | { text: "String", isCorrect: true }, 179 | { text: "ArrayList", isCorrect: false }, 180 | { text: "HashMap", isCorrect: false }, 181 | { text: "StringBuilder", isCorrect: false }, 182 | ], 183 | difficulty: "Medium", 184 | }, 185 | { 186 | text: "What is the output of `5 + '5'` in JavaScript?", 187 | options: [ 188 | { text: "55", isCorrect: true }, 189 | { text: "10", isCorrect: false }, 190 | { text: "NaN", isCorrect: false }, 191 | { text: "Error", isCorrect: false }, 192 | ], 193 | difficulty: "Medium", 194 | }, 195 | { 196 | text: "Which of these is a strongly typed programming language?", 197 | options: [ 198 | { text: "Java", isCorrect: true }, 199 | { text: "JavaScript", isCorrect: false }, 200 | { text: "Python", isCorrect: false }, 201 | { text: "Ruby", isCorrect: false }, 202 | ], 203 | difficulty: "Easy", 204 | }, 205 | ]; 206 | 207 | module.exports = programmingQuestions; 208 | -------------------------------------------------------------------------------- /public/categories/image--timeline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/chart.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as RechartsPrimitive from "recharts" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | // Format: { THEME_NAME: CSS_SELECTOR } 9 | const THEMES = { light: "", dark: ".dark" } as const 10 | 11 | export type ChartConfig = { 12 | [k in string]: { 13 | label?: React.ReactNode 14 | icon?: React.ComponentType 15 | } & ( 16 | | { color?: string; theme?: never } 17 | | { color?: never; theme: Record } 18 | ) 19 | } 20 | 21 | type ChartContextProps = { 22 | config: ChartConfig 23 | } 24 | 25 | const ChartContext = React.createContext(null) 26 | 27 | function useChart() { 28 | const context = React.useContext(ChartContext) 29 | 30 | if (!context) { 31 | throw new Error("useChart must be used within a ") 32 | } 33 | 34 | return context 35 | } 36 | 37 | const ChartContainer = React.forwardRef< 38 | HTMLDivElement, 39 | React.ComponentProps<"div"> & { 40 | config: ChartConfig 41 | children: React.ComponentProps< 42 | typeof RechartsPrimitive.ResponsiveContainer 43 | >["children"] 44 | } 45 | >(({ id, className, children, config, ...props }, ref) => { 46 | const uniqueId = React.useId() 47 | const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` 48 | 49 | return ( 50 | 51 |
60 | 61 | 62 | {children} 63 | 64 |
65 |
66 | ) 67 | }) 68 | ChartContainer.displayName = "Chart" 69 | 70 | const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { 71 | const colorConfig = Object.entries(config).filter( 72 | ([, config]) => config.theme || config.color 73 | ) 74 | 75 | if (!colorConfig.length) { 76 | return null 77 | } 78 | 79 | return ( 80 |