Skip to content

Commit 61f4efb

Browse files
authored
Merge pull request #180 from AlanRacciatti/feature_quizzes
feature: multiple choice in quizzes
2 parents c1d1dea + 918db58 commit 61f4efb

File tree

6 files changed

+142
-149
lines changed

6 files changed

+142
-149
lines changed

components/mdx/Question.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Box, VStack, Text, Button, useToast } from '@chakra-ui/react'
2-
import React, { FC, useState } from 'react'
2+
import React, { FC, useState, Dispatch, SetStateAction } from 'react'
33

44
interface QuestionProps {
55
question: string
@@ -17,19 +17,26 @@ interface Question {
1717

1818
const Question: FC<QuestionProps> = (props: QuestionProps) => {
1919
const question: Question = require(`../../utils/questions/${props.question}.json`)
20-
const [optionSelected, setOptionSelected]: [
21-
number,
22-
React.Dispatch<React.SetStateAction<number>>,
23-
] = useState(-1)
24-
const [answersSubmitted, setAnswersSubmitted] = useState(false)
20+
const [optionsSelected, setOptionsSelected]: [
21+
number[],
22+
Dispatch<SetStateAction<number[]>>,
23+
] = useState([-1])
2524
const toast = useToast()
2625

2726
const selectAnswer = (optionIndex: number) => {
28-
setOptionSelected(optionIndex)
27+
if (optionsSelected.includes(optionIndex)) {
28+
return setOptionsSelected(
29+
optionsSelected.filter((o) => o !== optionIndex),
30+
)
31+
}
32+
33+
setOptionsSelected(
34+
[...optionsSelected, optionIndex].filter((o) => o !== -1), // Remove the -1 of the state initialization
35+
)
2936
}
3037

3138
const getOptionBackground = (optionIndex: number) => {
32-
if (optionSelected == optionIndex) {
39+
if (optionsSelected.includes(optionIndex)) {
3340
return 'yellow.600'
3441
}
3542
return 'gray.600'
@@ -66,13 +73,23 @@ const Question: FC<QuestionProps> = (props: QuestionProps) => {
6673
}
6774

6875
const submit = () => {
69-
if (optionSelected === -1) {
76+
if (optionsSelected.includes(-1)) {
7077
return quizNotAnswered()
7178
}
7279

73-
if (question.options[optionSelected].correct) {
74-
return quizSuccessToast()
75-
}
80+
const correctAnswers = question.options.filter((o) => o.correct).length
81+
82+
let success = true
83+
let correctAnswersCount = 0
84+
85+
optionsSelected.forEach((o) => {
86+
if (!question.options[o].correct) success = false
87+
correctAnswersCount++
88+
})
89+
90+
if (correctAnswers !== correctAnswersCount) success = false
91+
92+
if (success) return quizSuccessToast()
7693

7794
return quizFailedToast()
7895
}

components/mdx/Quiz.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import {
1313
useToast,
1414
} from '@chakra-ui/react'
1515
import React, { FC, useState } from 'react'
16+
import {
17+
getCorrectAnswersIndexes,
18+
haveSameElements,
19+
} from '../../utils/QuizHelpers'
1620

1721
interface QuizProps {
1822
quiz: string
@@ -34,7 +38,7 @@ interface Quiz {
3438
}
3539

3640
interface Answers {
37-
[index: string]: number
41+
[index: string]: number[]
3842
}
3943

4044
const Quiz: FC<QuizProps> = (props: QuizProps) => {
@@ -65,7 +69,18 @@ const Quiz: FC<QuizProps> = (props: QuizProps) => {
6569

6670
const selectAnswer = (answerIndex: number) => {
6771
let newAnswers: Answers = { ...answers }
68-
newAnswers[currentQuestionIndex.toString()] = answerIndex
72+
73+
if (newAnswers[currentQuestionIndex]?.includes(answerIndex)) {
74+
newAnswers[currentQuestionIndex.toString()] = newAnswers[
75+
currentQuestionIndex
76+
]?.filter((a) => a !== answerIndex)
77+
} else {
78+
newAnswers[currentQuestionIndex.toString()] = [
79+
...(answers[currentQuestionIndex] || []),
80+
answerIndex,
81+
]
82+
}
83+
6984
setAnswers(newAnswers)
7085
}
7186

@@ -74,12 +89,12 @@ const Quiz: FC<QuizProps> = (props: QuizProps) => {
7489
correctAnswers &&
7590
correctAnswers.indexOf(currentQuestionIndex) !== -1 &&
7691
quiz.questions[currentQuestionIndex].options[optionIndex].correct &&
77-
answers[currentQuestionIndex] === optionIndex
92+
answers[currentQuestionIndex]?.includes(optionIndex)
7893
) {
7994
return 'green.500'
8095
}
8196

82-
if (answers[currentQuestionIndex] == optionIndex) {
97+
if (answers[currentQuestionIndex]?.includes(optionIndex)) {
8398
return 'yellow.600'
8499
}
85100
return 'gray.600'
@@ -120,23 +135,24 @@ const Quiz: FC<QuizProps> = (props: QuizProps) => {
120135
return quizNotAnswered()
121136
}
122137

123-
let hasWrongAnswers = false
124138
let wrongAnswersCounter = 0
125139

126140
const newCorrectAnswers: number[] = []
127141

128-
quiz.questions.forEach((q, index) => {
129-
if (!q.options[answers[index]].correct) {
130-
hasWrongAnswers = true
142+
quiz.questions.forEach((question, index) => {
143+
let correctAnswersIndexes = getCorrectAnswersIndexes(question)
144+
145+
if (!haveSameElements(answers[index], correctAnswersIndexes)) {
131146
wrongAnswersCounter++
132-
} else {
133-
newCorrectAnswers.push(index)
147+
return
134148
}
149+
150+
newCorrectAnswers.push(index)
135151
})
136152

137153
setCorrectAnswers(newCorrectAnswers)
138154

139-
if (hasWrongAnswers) {
155+
if (wrongAnswersCounter >= 1) {
140156
return quizFailedToast(wrongAnswersCounter)
141157
}
142158

@@ -146,6 +162,7 @@ const Quiz: FC<QuizProps> = (props: QuizProps) => {
146162
const cancelQuiz = () => {
147163
setAnswers({})
148164
setShowQuiz(false)
165+
setCorrectAnswers(null)
149166
setCurrentQuestionIndex(0)
150167
}
151168

utils/QuizHelpers.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
interface Question {
2+
question: string
3+
options: [
4+
{
5+
answer: string
6+
correct?: boolean
7+
},
8+
]
9+
}
10+
11+
export function getCorrectAnswersIndexes(question: Question): number[] {
12+
return question.options
13+
.filter((option) => option.correct)
14+
.map((correctOption) => question.options.indexOf(correctOption))
15+
}
16+
17+
export function haveSameElements(arr1: number[], arr2: number[]): boolean {
18+
if (arr1.length !== arr2.length) {
19+
return false
20+
}
21+
let arr1Sorted = arr1.sort()
22+
let arr2Sorted = arr2.sort()
23+
for (let i = 0; i < arr1Sorted.length; i++) {
24+
if (arr1Sorted[i] !== arr2Sorted[i]) {
25+
return false
26+
}
27+
}
28+
return true
29+
}

utils/questions/question-1.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
{
2-
"question": "Apart from a user wallet, what else uses a blockchain (Ethereum) address?",
2+
"question": "Which of these properties does Ethereum have?",
33
"options": [
44
{
5-
"answer": "Transactions"
5+
"answer": "Permissioned"
66
},
77
{
8-
"answer": "Smart contracts",
8+
"answer": "Trustless",
99
"correct": true
1010
},
1111
{
12-
"answer": "NFTs"
12+
"answer": "Decentralized",
13+
"correct": true
1314
}
1415
]
1516
}

utils/quizzes/lesson-1.json

Lines changed: 0 additions & 122 deletions
This file was deleted.

utils/quizzes/quiz-1.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"title": "Quiz: Lesson 1",
3+
"questions": [
4+
{
5+
"question": "Where do the values for event parameters get stored?",
6+
"options": [
7+
{
8+
"answer": "In the transaction data"
9+
},
10+
{
11+
"answer": "In transaction logs",
12+
"correct": true
13+
},
14+
{
15+
"answer": "They are not stored"
16+
}
17+
]
18+
},
19+
{
20+
"question": "Which of these properties does Ethereum have?",
21+
"options": [
22+
{
23+
"answer": "Permissioned"
24+
},
25+
{
26+
"answer": "Trustless",
27+
"correct": true
28+
},
29+
{
30+
"answer": "Decentralized",
31+
"correct": true
32+
}
33+
]
34+
},
35+
{
36+
"question": "What can we use a smart contract for?",
37+
"options": [
38+
{
39+
"answer": "Store or transfer value or services without the need of a central authority",
40+
"correct": true
41+
},
42+
{
43+
"answer": "Make a breakfast"
44+
},
45+
{
46+
"answer": "Verifying the authenticity of documents and information."
47+
}
48+
]
49+
}
50+
]
51+
}

0 commit comments

Comments
 (0)