├── .eslintrc.json
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
├── _document.jsx
├── api
│ └── question.js
└── index.jsx
├── postcss.config.js
├── public
├── favicon.ico
└── vercel.svg
├── styles
└── globals.css
└── tailwind.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.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 | node_modules
38 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | -
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Galih Sukristyan Saputra
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Simple NextJS Quiz App
4 |
5 |
6 |
7 |
8 |
9 | [](https://github.com/masgalih320/quizapp-nextjs/graphs/contributors)
10 | 
11 | 
12 | [](LICENSE)
13 |
14 |
15 |
16 |
17 | Simple NextJS Quiz App made with NextJS and TailwindCSS
18 |
19 |
20 | ## Features
21 |
22 | - Timer
23 | - Question bank stored on API endpoint on `pages/api/question.js` file and easy to modify the question
24 | - Game Over screen
25 |
26 | ## Future update
27 |
28 | - Before play screen (user can login/input username here)
29 | - Choose quiz room screen (public quiz)
30 | - Quiz room (room code for private quiz)
31 | - Responsive page
32 | - More interactive
33 |
34 | ## Installation Guide
35 |
36 | ### Build setup
37 |
38 | ```bash
39 | # clone the repository
40 | $ git clone https://github.com/masgalih320/quizapp-nextjs quiz
41 |
42 | # navigate to the folder
43 | $ cd quiz
44 | ```
45 |
46 | After clone the repository and navigate to the folder, you can use few commands below
47 |
48 | ```bash
49 | # install dependencies
50 | $ npm install
51 |
52 | # serve with hot reload at localhost:3000
53 | $ npm run dev
54 |
55 | # build for production and launch server
56 | $ npm run build
57 | $ npm run start
58 | ```
59 |
60 | For detailed explanation on how things work, checkout [Next.JS docs](https://nextjs.org)
61 |
62 | ## Contributing
63 |
64 | See contributing Guide [here](./CONTRIBUTING.md)
65 |
66 | ## License
67 |
68 | Simple NextJS Quiz App is under MIT License
69 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quiz",
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 | "next": "13.0.0",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0"
15 | },
16 | "devDependencies": {
17 | "autoprefixer": "^10.4.12",
18 | "eslint": "8.26.0",
19 | "eslint-config-next": "13.0.0",
20 | "postcss": "^8.4.18",
21 | "tailwindcss": "^3.2.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 | Simple Quiz App with NextJS
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
--------------------------------------------------------------------------------
/pages/api/question.js:
--------------------------------------------------------------------------------
1 | // Define API routes for getting question bank
2 | export default function handler(req, res) {
3 | return res.status(200).json({
4 | status: 200,
5 | msg: "success",
6 | data: [
7 | {
8 | time: 10000,
9 | question: "Siapakah presiden Negara Republik Indonesia ke-1?",
10 | answer: "Ir. Soekarno",
11 | choices: ["WR. Soepratman", "Soeharto", "Puan Maharani", "Ir. Soekarno"],
12 | },
13 | {
14 | time: 5000,
15 | question: "Kerusuhan Banjarmasin terjadi pada tahun berapa?",
16 | answer: 1997,
17 | choices: [1997, 2002, 2013, 1945],
18 | },
19 | {
20 | time: 20000,
21 | question: "Kepanjangan DPR adalah?",
22 | answer: "Dewan Perwakilan Rakyat",
23 | choices: ["Dewan Perwakilan Daerah", "Dewan Perwakilan Rakyat", "Dewan Pribumi Rahasia", "Dewan Pengkhianat Rakyat"],
24 | },
25 | {
26 | time: 10000,
27 | question: "Kepanjangan TNI adalah?",
28 | answer: "Tentara Nasional Indonesia",
29 | choices: ["Tentara Nasional Indonesia", "Tau Nama Indonesia", "Takutnya Negara Indonesia", "Tinggal Nama Ini"],
30 | },
31 | ],
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router"
2 | import { useEffect, useState } from "react"
3 |
4 | // test
5 | export default function Home({ question }) {
6 | const router = useRouter()
7 | const [isOver, setIsOver] = useState(false)
8 | const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
9 | const [currentQuestionAnswer, setCurrentQuestionAnswer] = useState(question[currentQuestionIndex] ? question[currentQuestionIndex].answer : "")
10 | const [correctAnswerTotal, setCorrectAnswerTotal] = useState(0)
11 | const [incorrectAnswerTotal, setIncorrectAnswerTotal] = useState(0)
12 | const [timer, setTimer] = useState(100)
13 |
14 | useEffect(() => {
15 | if (!isOver) {
16 | const interval = setInterval(() => {
17 | if (timer > 0) setTimer((timer) => Math.floor(timer - Math.floor(question[currentQuestionIndex].time / 1000)))
18 | }, 1000)
19 |
20 | if (timer == 0 && question.length - 1 == currentQuestionIndex) setIsOver(true)
21 | if (timer == 0 && isOver == false) setCurrentQuestionIndex(currentQuestionIndex + 1)
22 | if (timer == 0 && isOver == false) setTimer(100)
23 | if (timer == 0 && isOver == false) setIncorrectAnswerTotal(incorrectAnswerTotal + 1)
24 |
25 | return () => clearInterval(interval)
26 | } else {
27 | setTimer(0)
28 | }
29 | }, [timer])
30 |
31 | function handleClick(ans) {
32 | setCurrentQuestionIndex((currentQuestionIndex) => {
33 | ans === currentQuestionAnswer ? setCorrectAnswerTotal(correctAnswerTotal + 1) : setIncorrectAnswerTotal(incorrectAnswerTotal + 1)
34 | if (timer !== 0 && isOver == false && question.length - 1 == currentQuestionIndex) return setIsOver(true)
35 | if (timer !== 0 && isOver == false) setTimer(100)
36 | if (timer !== 0 && isOver == false) setCurrentQuestionAnswer(question[currentQuestionIndex + 1].answer)
37 | if (timer !== 0 && isOver == false) return currentQuestionIndex + 1
38 | })
39 | }
40 |
41 | return (
42 |
43 | {isOver ?
44 |
45 |
46 |
Your score
47 |
48 |
49 |
{correctAnswerTotal} / {question.length}
50 |
Incorrect Answer: {incorrectAnswerTotal}
51 |
router.reload()}>Play again
52 |
53 |
54 |
55 |
56 |
: ""}
57 |
58 |
59 |
60 |
61 |
62 | Question: {question.length}
63 | Correct: {correctAnswerTotal}
64 | Incorrect: {incorrectAnswerTotal}
65 |
66 |
{question.length - 1 >= currentQuestionIndex ? question[currentQuestionIndex].question : ""}
67 |
68 |
69 |
70 | {question.length - 1 >= currentQuestionIndex ? question[currentQuestionIndex].choices.map((seg) => {
71 | return (
72 |
handleClick(seg)} className="p-4 bg-blue-400 border-blue-500 hover:bg-blue-600 hover:border-blue-600 duration-300 border-2 h-80 w-full rounded-lg cursor-pointer flex items-center justify-center">
73 |
{seg}
74 |
75 | )
76 | }) : ""}
77 |
78 |
79 | )
80 | }
81 |
82 | export async function getStaticProps() {
83 | const getQuestion = await fetch(`http://localhost:3000/api/question`)
84 | const question = await getQuestion.json()
85 | return {
86 | props: {
87 | question: question.data
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sukristyan/quizapp-nextjs/7eca8e12e50fd2b796565dbc46fbfa41bd96efdd/public/favicon.ico
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------