├── .eslintrc.json ├── public └── world.jpg ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── loading.js │ ├── layout.jsx │ ├── page.jsx │ ├── quiz │ │ ├── scoreboard.jsx │ │ ├── page.jsx │ │ ├── client-container.jsx │ │ ├── answer.jsx │ │ └── quiz.jsx │ ├── global-error.js │ └── form.jsx ├── error.js └── types.js ├── jsconfig.json ├── next.config.mjs ├── postcss.config.js ├── .prettierrc ├── tailwind.config.js ├── .gitignore ├── package.json ├── README.md └── pnpm-lock.yaml /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zougari47/quizify/HEAD/public/world.jpg -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zougari47/quizify/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const error = () => { 4 | return
error
5 | } 6 | 7 | export default error 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "bracketSameLine": true, 6 | "plugins": ["prettier-plugin-tailwindcss"] 7 | } 8 | -------------------------------------------------------------------------------- /src/app/loading.js: -------------------------------------------------------------------------------- 1 | const Loading = () => { 2 | return ( 3 |
4 | 5 |
6 | ) 7 | } 8 | 9 | export default Loading 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | 9 | plugins: [require('daisyui')], 10 | } 11 | -------------------------------------------------------------------------------- /.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.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/app/layout.jsx: -------------------------------------------------------------------------------- 1 | import { Inter } from 'next/font/google' 2 | import './globals.css' 3 | 4 | const inter = Inter({ subsets: ['latin'] }) 5 | 6 | export const metadata = { 7 | title: 'Quiz App', 8 | description: 'Quiz App with Next js', 9 | } 10 | 11 | export default function RootLayout({ children }) { 12 | return ( 13 | 14 | 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quiz-next", 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 | }, 11 | "dependencies": { 12 | "html-entities": "^2.5.2", 13 | "lucide-react": "^0.363.0", 14 | "next": "14.1.4", 15 | "react": "^18", 16 | "react-dom": "^18" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^10.0.1", 20 | "daisyui": "^4.7.3", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.1.4", 23 | "postcss": "^8", 24 | "prettier-plugin-tailwindcss": "^0.5.12", 25 | "tailwindcss": "^3.3.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} TriviaCategory 3 | * @property {number} id - The unique identifier for the trivia category. 4 | * @property {string} name - The name of the trivia category. 5 | */ 6 | 7 | /** 8 | * @typedef {Object} TriviaData 9 | * @property {Array.} trivia_categories - An array of trivia categories. 10 | */ 11 | 12 | /** 13 | * @type {TriviaData} triviaObject 14 | */ 15 | 16 | /** 17 | * @typedef {Object} Quiz 18 | * @property {string} type 19 | * @property {string} difficulty 20 | * @property {string} category 21 | * @property {string} question 22 | * @property {string} correct_answer 23 | * @property {string[]} incorrect_answers 24 | */ 25 | 26 | exports.unused = {} 27 | -------------------------------------------------------------------------------- /src/app/page.jsx: -------------------------------------------------------------------------------- 1 | import Form from '@/app/form' 2 | import types from '@/types' 3 | 4 | /** 5 | * Fetches trivia categories from the Open Trivia Database API. 6 | * @returns {Promise} 7 | */ 8 | async function getCategories() { 9 | const res = await fetch('https://opentdb.com/api_category.php') 10 | 11 | if (!res.ok) { 12 | // This will activate the closest `error.js` Error Boundary 13 | throw new Error('Failed to fetch categories') 14 | } 15 | 16 | return res.json() 17 | } 18 | 19 | export default async function HomePage() { 20 | const categories = await getCategories() 21 | 22 | return ( 23 |
24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/app/quiz/scoreboard.jsx: -------------------------------------------------------------------------------- 1 | import { RotateCcw } from 'lucide-react' 2 | import Link from 'next/link' 3 | 4 | /** 5 | * @param {object} props 6 | * @param {string} props.score 7 | * @returns {JSX.Element} 8 | */ 9 | const ScoreBoard = ({ score }) => { 10 | return ( 11 |
12 |
13 |

14 | 15 | Your Score is {score} 16 | 17 |

18 |
19 |
20 | 21 | Play Again 22 | 23 | 24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default ScoreBoard 31 | -------------------------------------------------------------------------------- /src/app/quiz/page.jsx: -------------------------------------------------------------------------------- 1 | import ClientContainer from './client-container' 2 | import types from '@/types' 3 | 4 | export const dynamic = 'force-dynamic' 5 | 6 | /** 7 | * Fetches Quizzes from the Open Trivia Database API 8 | * @param {object} searchParams 9 | * @param {number} searchParams.amount 10 | * @param {string} searchParams.category 11 | * @param {string} searchParams.difficulty 12 | * @param {string} searchParams.type 13 | * @returns {Promise<{response_code:number,results:types.Quiz[]}>} 14 | */ 15 | async function getQuizzes(searchParams = { amount: 5 }) { 16 | const filteredParams = Object.fromEntries( 17 | Object.entries(searchParams).filter(([, value]) => value !== 'any'), 18 | ) 19 | 20 | const res = await fetch( 21 | `https://opentdb.com/api.php?${new URLSearchParams(filteredParams).toString()}`, 22 | ) 23 | 24 | if (!res.ok) { 25 | // This will activate the closest `error.js` Error Boundary 26 | throw new Error('Failed to fetch quizzes') 27 | } 28 | 29 | return res.json() 30 | } 31 | 32 | const QuizPage = async ({ searchParams }) => { 33 | const quizzes = await getQuizzes(searchParams) 34 | 35 | return 36 | } 37 | 38 | export default QuizPage 39 | -------------------------------------------------------------------------------- /src/app/quiz/client-container.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | // TODO 4 | // - find solution for encoding 5 | // - add animation 6 | // - score bord 7 | // - add icon to next button and try to change the color 8 | 9 | import { useEffect, useState } from 'react' 10 | import Quiz from './quiz' 11 | import types from '@/types' 12 | import ScoreBoard from './scoreboard' 13 | 14 | /** 15 | * @param {object} props 16 | * @param {types.Quiz[]} props.quizzes 17 | * @returns {JSX.Element} 18 | */ 19 | const ClientContainer = ({ quizzes }) => { 20 | const [currentQuizIndex, setCurrentQuizIndex] = useState(0) 21 | const [score, setScore] = useState(0) 22 | const [isClient, setIsClient] = useState(false) 23 | 24 | useEffect(() => { 25 | setIsClient(true) 26 | }, []) 27 | 28 | if (!isClient) return null 29 | 30 | return currentQuizIndex === quizzes.length ? ( 31 | 32 | ) : ( 33 | 38 | currentQuizIndex < quizzes.length && 39 | setCurrentQuizIndex((prev) => prev + 1) 40 | } 41 | /> 42 | ) 43 | } 44 | 45 | export default ClientContainer 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/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.js`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 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/deployment) for more details. 37 | -------------------------------------------------------------------------------- /src/app/global-error.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export default function GlobalError({ error, reset }) { 4 | return ( 5 | 6 | 7 |
8 |
9 |
10 |
11 | 16 | 21 | 26 | 27 |
28 |
29 |

30 | 500 - Server error 31 |

32 |

33 | Oops something went wrong. Try to refresh this page. 34 |

35 |
36 | 39 |
40 |
41 |
42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/app/quiz/answer.jsx: -------------------------------------------------------------------------------- 1 | import { CircleX } from 'lucide-react' 2 | import { Circle } from 'lucide-react' 3 | import { CheckCircle2 } from 'lucide-react' 4 | 5 | /** 6 | * @param {object} props 7 | * @param {string} props.answer 8 | * @param {string} props.name 9 | * @param {string} props.selectedAnswer 10 | * @param {string} props.correct_answer 11 | * @param {boolean} props.hasAnswered 12 | * @param {void} props.onAnswerChange 13 | * @returns {JSX.Element} 14 | */ 15 | const Answer = ({ 16 | answer, 17 | name, 18 | selectedAnswer, 19 | hasAnswered, 20 | correct_answer, 21 | onAnswerChange, 22 | }) => { 23 | const id = answer?.replace(/[^\w\s]/gi, '').replace(/\s+/g, '') 24 | 25 | console.log({ answer, correct_answer }) 26 | 27 | return ( 28 |