Skip to content

Commit 9f2977b

Browse files
authored
Merge pull request #10320 from ethereum/feat-learning-quizzes-hub
feat: Learning Quizzes Hub
2 parents 683e7a6 + a8e4157 commit 9f2977b

29 files changed

+1415
-271
lines changed

redirects.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@
251251
"fromPath": "/upgrades",
252252
"toPath": "/en/roadmap"
253253
},
254+
{
255+
"fromPath": "/quizzes",
256+
"toPath": "/en/quizzes/"
257+
},
254258
{
255259
"fromPath": "/*/upgrades",
256260
"toPath": "/:splat/roadmap"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { progressAnatomy as parts } from "@chakra-ui/anatomy"
2+
import { defineStyleConfig } from "@chakra-ui/react"
3+
import { createMultiStyleConfigHelpers } from "@chakra-ui/styled-system"
4+
5+
const { definePartsStyle } = createMultiStyleConfigHelpers(parts.keys)
6+
7+
const baseStyle = definePartsStyle(() => ({
8+
track: {
9+
bg: "primaryLight",
10+
// `borderRadius` applies to both track and `filledTrack` (https://github.com/chakra-ui/chakra-ui/pull/2946)
11+
borderRadius: "full",
12+
},
13+
filledTrack: {
14+
bgColor: "primary",
15+
},
16+
}))
17+
18+
// see https://github.com/chakra-ui/chakra-ui/blob/38acfe89c5d1f1edc67bbc44e2edd38980ca3e08/packages/components/theme/src/components/progress.ts#L63
19+
const sizes = {
20+
xs: definePartsStyle({
21+
track: { h: "1" },
22+
}),
23+
sm: definePartsStyle({
24+
track: { h: "2" },
25+
}),
26+
md: definePartsStyle({
27+
track: { h: "2.5" },
28+
}),
29+
lg: definePartsStyle({
30+
track: { h: "3" },
31+
}),
32+
}
33+
34+
export const Progress = defineStyleConfig({
35+
sizes,
36+
baseStyle,
37+
defaultProps: {
38+
size: "md",
39+
},
40+
})

src/@chakra-ui/gatsby-plugin/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Link } from "./Link"
44
import { Tag } from "./Tag"
55
import { Modal } from "./Modal"
66
import { Checkbox } from "./Checkbox"
7+
import { Progress } from "./Progress"
78
import { Tabs } from "./Tabs"
89
import {
910
accordionDefaultTheme,
@@ -40,6 +41,7 @@ export default {
4041
Link,
4142
Menu: menuDefaultTheme,
4243
Modal,
44+
Progress,
4345
Select: selectDefaultTheme,
4446
Spinner: spinnerDefaultTheme,
4547
Switch: switchDefaultTheme,

src/components/Footer.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ const Footer: React.FC<IProps> = () => {
162162
text: t("zero-knowledge-proofs"),
163163
to: "/zero-knowledge-proofs/",
164164
},
165+
{
166+
text: t("quizzes-title"),
167+
to: "/quizzes/",
168+
},
165169
],
166170
},
167171
{

src/components/Nav/useNav.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ export const useNav = ({ path }: { path: string }) => {
116116
text: t("smart-contracts"),
117117
to: "/smart-contracts/",
118118
},
119+
{
120+
text: t("quizzes-title"),
121+
to: "/quizzes/",
122+
},
119123
],
120124
},
121125
{

src/components/Quiz/QuizItem.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useContext } from "react"
2+
import { Box, Flex, ListItem, Stack, Text } from "@chakra-ui/react"
3+
import { useTranslation } from "gatsby-plugin-react-i18next"
4+
5+
import Button from "../Button"
6+
import Translation from "../Translation"
7+
import Tag from "../Tag"
8+
import { GreenTickIcon } from "../icons/quiz"
9+
10+
import { QuizzesHubContext } from "./context"
11+
12+
import { trackCustomEvent } from "../../utils/matomo"
13+
14+
import { QuizzesListItem } from "../../types"
15+
16+
import allQuizzesData from "../../data/quizzes"
17+
18+
const QuizItem: React.FC<QuizzesListItem> = (props) => {
19+
const { id, level, quizHandler, modalHandler } = props
20+
const {
21+
userStats: { completed },
22+
} = useContext(QuizzesHubContext)
23+
const numberOfQuestions = allQuizzesData[id].questions.length
24+
const isCompleted = JSON.parse(completed)[id][0]
25+
26+
const { t } = useTranslation()
27+
28+
const handleStart = () => {
29+
quizHandler(id)
30+
modalHandler(true)
31+
32+
trackCustomEvent({
33+
eventCategory: "quiz_hub_events",
34+
eventAction: "quizzes click",
35+
eventName: `${id}`,
36+
})
37+
}
38+
39+
return (
40+
<ListItem
41+
color={isCompleted ? "bodyMedium" : "text"}
42+
fontWeight="bold"
43+
px={{ base: 0, lg: 4 }}
44+
py={4}
45+
borderBottom="1px solid"
46+
borderColor="disabled"
47+
mb={0}
48+
sx={{ counterIncrement: "list-counter" }}
49+
>
50+
<Flex
51+
justifyContent="space-between"
52+
alignItems={{ base: "flex-start", lg: "center" }}
53+
direction={{ base: "column", lg: "row" }}
54+
>
55+
<Stack mb={{ base: 5, lg: 0 }}>
56+
<Flex gap={2} alignItems="center">
57+
<Text
58+
color={isCompleted ? "bodyMedium" : "text"}
59+
fontWeight="bold"
60+
mb={0}
61+
_before={{
62+
content: 'counter(list-counter) ". "',
63+
}}
64+
>
65+
<Translation id={allQuizzesData[id].title} />
66+
</Text>
67+
68+
{/* Show green tick if quizz was completed only */}
69+
<Box display={isCompleted ? "flex" : "none"}>
70+
<GreenTickIcon />
71+
</Box>
72+
</Flex>
73+
74+
{/* Labels */}
75+
<Flex gap={3}>
76+
{/* number of questions - label */}
77+
<Tag
78+
label={t(`${numberOfQuestions} ${t("questions")}`)}
79+
ml={{ lg: -2 }}
80+
/>
81+
82+
{/* difficulty - label */}
83+
<Tag label={level.toUpperCase()} />
84+
</Flex>
85+
</Stack>
86+
87+
{/* Start Button */}
88+
<Box w={{ base: "full", lg: "auto" }}>
89+
<Button
90+
variant="outline-color"
91+
w={{ base: "full", lg: "auto" }}
92+
onClick={handleStart}
93+
>
94+
<Translation id="start" />
95+
</Button>
96+
</Box>
97+
</Flex>
98+
</ListItem>
99+
)
100+
}
101+
102+
export default QuizItem

src/components/Quiz/QuizRadioGroup.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Import libraries
21
import React, { useMemo } from "react"
32
import {
43
Box,
@@ -12,28 +11,24 @@ import {
1211
} from "@chakra-ui/react"
1312
import { useTranslation } from "gatsby-plugin-react-i18next"
1413

15-
// Components
1614
import Translation from "../Translation"
1715

18-
// Import types
19-
import { Question } from "../../types"
20-
21-
// Utils
2216
import { TranslationKey } from "../../utils/translations"
2317

24-
// Interfaces
25-
export interface CustomRadioProps extends RadioProps {
18+
import { Question } from "../../types"
19+
20+
interface CustomRadioProps extends RadioProps {
2621
index: number
2722
label: string
2823
}
29-
export interface IProps {
24+
25+
interface IProps {
3026
questionData: Question
3127
showAnswer: boolean
3228
handleSelection: (answerId: string) => void
3329
selectedAnswer: string | null
3430
}
3531

36-
// Component
3732
const QuizRadioGroup: React.FC<IProps> = ({
3833
questionData,
3934
showAnswer,
@@ -51,7 +46,6 @@ const QuizRadioGroup: React.FC<IProps> = ({
5146
if (!selectedAnswer) return ""
5247
return answers.filter(({ id }) => id === selectedAnswer)[0].explanation
5348
}, [selectedAnswer])
54-
5549
const isSelectedCorrect = useMemo<boolean>(
5650
() => correctAnswerId === selectedAnswer,
5751
[selectedAnswer]
@@ -128,9 +122,15 @@ const QuizRadioGroup: React.FC<IProps> = ({
128122
// Render QuizRadioGroup
129123
return (
130124
<Flex {...getRootProps()} direction="column" w="100%">
131-
<Text fontWeight="700" fontSize="2xl" mb={6}>
125+
<Text
126+
textAlign={{ base: "center", md: "left" }}
127+
fontWeight="700"
128+
fontSize="2xl"
129+
mb={6}
130+
>
132131
{t(prompt)}
133132
</Text>
133+
134134
<Flex direction="column" gap={4}>
135135
{answers.map(({ id, label }, index) => {
136136
const display =
@@ -146,6 +146,7 @@ const QuizRadioGroup: React.FC<IProps> = ({
146146
)
147147
})}
148148
</Flex>
149+
149150
{showAnswer && (
150151
<Box mt={5}>
151152
<Text fontWeight="bold" mt={0} mb={2}>

src/components/Quiz/QuizSummary.tsx

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,63 @@
1-
// Import libraries
2-
import React from "react"
1+
import React, { useEffect } from "react"
32
import { Box, Flex, Text, useMediaQuery } from "@chakra-ui/react"
43
import { useI18next } from "gatsby-plugin-react-i18next"
54

6-
// Components
75
import Translation from "../Translation"
86

9-
// Import utilities
107
import { numberToPercent } from "../../utils/numberToPercent"
8+
import { updateUserStats } from "./utils"
9+
import { UserStats } from "../../types"
1110

12-
// Interfaces
13-
export interface IProps {
14-
correctCount: number
11+
interface IProps {
12+
quizKey: string
13+
numberOfCorrectAnswers: number
1514
isPassingScore: boolean
1615
questionCount: number
1716
ratioCorrect: number
17+
quizScore: number
18+
setUserStats: (stats: UserStats) => void
1819
}
1920

20-
// Component
2121
const QuizSummary: React.FC<IProps> = ({
22-
correctCount,
22+
quizKey,
23+
numberOfCorrectAnswers,
2324
isPassingScore,
2425
questionCount,
2526
ratioCorrect,
27+
quizScore,
28+
setUserStats,
2629
}) => {
2730
const { language } = useI18next()
2831
const [largerThanMobile] = useMediaQuery("(min-width: 30em)")
2932

3033
const valueStyles = { fontWeight: "700", mb: 2 }
3134
const labelStyles = { fontSize: "sm", m: 0, color: "disabled" }
3235

33-
// Render QuizSummary component
36+
// QuizSummary is rendered when user has finished the quiz, proper time to update the stats
37+
useEffect(() => {
38+
updateUserStats({
39+
quizKey,
40+
quizScore,
41+
numberOfCorrectAnswers,
42+
setUserStats,
43+
})
44+
}, [])
45+
3446
return (
3547
<Box w="full" fontSize={["xl", "2xl"]}>
36-
<Text fontWeight="700" textAlign="center">
37-
{isPassingScore ? "You passed the quiz!" : "Your results"}
48+
<Text
49+
fontWeight="700"
50+
textAlign="center"
51+
color={isPassingScore ? "success" : "body"}
52+
fontSize="3xl"
53+
>
54+
{isPassingScore ? (
55+
<Translation id="passed" />
56+
) : (
57+
<Translation id="your-results" />
58+
)}
3859
</Text>
60+
3961
<Flex
4062
p={4}
4163
justify="center"
@@ -64,17 +86,19 @@ const QuizSummary: React.FC<IProps> = ({
6486
<Translation id="score" />
6587
</Text>
6688
</Flex>
89+
6790
<Flex>
68-
<Text {...valueStyles}>+{correctCount}</Text>
91+
<Text {...valueStyles}>+{numberOfCorrectAnswers}</Text>
6992
<Text {...labelStyles}>
7093
<Translation id="correct" />
7194
</Text>
7295
</Flex>
96+
7397
{largerThanMobile && (
7498
<Flex>
7599
<Text {...valueStyles}>{questionCount}</Text>
76100
<Text {...labelStyles}>
77-
<Translation id="total" />
101+
<Translation id="questions" />
78102
</Text>
79103
</Flex>
80104
)}

0 commit comments

Comments
 (0)