From fa9d8650fa78f8f4581c313c6a309af028d73f17 Mon Sep 17 00:00:00 2001 From: Corwin Smith Date: Sat, 17 Sep 2022 15:00:13 +0200 Subject: [PATCH 01/56] base start to quiz component --- src/assets/trophy.svg | 11 +++++ src/components/Quiz/Quiz.tsx | 52 ++++++++++++++++++++++ src/components/Quiz/QuizQuestion.tsx | 39 ++++++++++++++++ src/data/learnQuzzes/index.ts | 8 ++++ src/data/learnQuzzes/whatIsEthereumQuiz.ts | 35 +++++++++++++++ src/intl/en/quizzes.json | 3 ++ src/pages-conditional/what-is-ethereum.tsx | 16 +++++++ 7 files changed, 164 insertions(+) create mode 100644 src/assets/trophy.svg create mode 100644 src/components/Quiz/Quiz.tsx create mode 100644 src/components/Quiz/QuizQuestion.tsx create mode 100644 src/data/learnQuzzes/index.ts create mode 100644 src/data/learnQuzzes/whatIsEthereumQuiz.ts create mode 100644 src/intl/en/quizzes.json diff --git a/src/assets/trophy.svg b/src/assets/trophy.svg new file mode 100644 index 00000000000..a571d7f990a --- /dev/null +++ b/src/assets/trophy.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/Quiz/Quiz.tsx b/src/components/Quiz/Quiz.tsx new file mode 100644 index 00000000000..62572e5db75 --- /dev/null +++ b/src/components/Quiz/Quiz.tsx @@ -0,0 +1,52 @@ +// Libraries +import React, { useState } from "react" +import { Box, Center, Text, useColorMode } from "@chakra-ui/react" + +// Components +import QuizQuestion from "./QuizQuestion" + +// Types +export interface IProps { + quiz: any +} + +const Quiz: React.FC = ({ quiz }) => { + const { colorMode } = useColorMode() + const [currentQuestionIndex, updateCurrentQuestionIndex] = useState(0) + const [quizData, setQuizdata] = useState( + quiz.questions.map((question) => { + return { + ...question, + userAnswer: undefined, + } + }) + ) + + return ( + +
+ + {quiz.title} + +
+
+ +
+
+ ) +} + +export default Quiz diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx new file mode 100644 index 00000000000..25f304d2173 --- /dev/null +++ b/src/components/Quiz/QuizQuestion.tsx @@ -0,0 +1,39 @@ +// Libraries +import React from "react" +import { Box, Text, useColorMode } from "@chakra-ui/react" + +// Types +export interface IProps { + questionData: any +} + +const QuizQuestion: React.FC = ({ questionData }) => { + const { colorMode } = useColorMode() + const { answers, question } = questionData + return ( + + + {question} + + {Object.keys(answers).map((key) => { + return ( + + + {key} + + + {answers[key].label} + + + ) + })} + + ) +} + +export default QuizQuestion diff --git a/src/data/learnQuzzes/index.ts b/src/data/learnQuzzes/index.ts new file mode 100644 index 00000000000..853ef5b152f --- /dev/null +++ b/src/data/learnQuzzes/index.ts @@ -0,0 +1,8 @@ +// Quizzes +import whatIsEthereumQuiz from "./whatIsEthereumQuiz" + +const quizzes = { + "what-is-ethereum": whatIsEthereumQuiz, +} + +export default quizzes diff --git a/src/data/learnQuzzes/whatIsEthereumQuiz.ts b/src/data/learnQuzzes/whatIsEthereumQuiz.ts new file mode 100644 index 00000000000..d5ccb87e745 --- /dev/null +++ b/src/data/learnQuzzes/whatIsEthereumQuiz.ts @@ -0,0 +1,35 @@ +const whatIsEthereumQuiz = { + title: "What is Ethereum Quiz", + questions: [ + { + correctAnswer: "b", + question: "What is the biggest difference between Bitcoin and Ethereum?", + answers: { + a: { + label: + "Bitcoin has middlemen that mediate transactions, Ethereum does not", + description: + "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + }, + b: { + label: "Ethereum is programmable, Bitcoin is not", + description: + "Unlike Bitcoin, Ethereum has smart contracts, expanding the utility of the network past payments.", + }, + c: { + label: + "Ethereum has middlemen that mediate transactions, Bitcoin does not", + description: + "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + }, + d: { + label: "Ethereum preserves privacy, Bitcoin does not", + description: + "Every node needs to be able to verify the blockchain's history from the beginning until the present. Therefore, there are no secrets on the blockchain. The only way to preserve privacy on the blockchain is to use private keys (and therefore identities) that cannot be traced back to you. This is possible on both Bitcoin and Ethereum.", + }, + }, + }, + ], +} + +export default whatIsEthereumQuiz diff --git a/src/intl/en/quizzes.json b/src/intl/en/quizzes.json new file mode 100644 index 00000000000..b282abfe306 --- /dev/null +++ b/src/intl/en/quizzes.json @@ -0,0 +1,3 @@ +{ + "quiz-test-your-knowledge": "Test your knowledge" +} diff --git a/src/pages-conditional/what-is-ethereum.tsx b/src/pages-conditional/what-is-ethereum.tsx index e3d088f9db7..55322f72a2f 100644 --- a/src/pages-conditional/what-is-ethereum.tsx +++ b/src/pages-conditional/what-is-ethereum.tsx @@ -35,6 +35,7 @@ import AdoptionChart from "../components/AdoptionChart" import EnergyConsumptionChart from "../components/EnergyConsumptionChart" import Slider, { EmblaSlide } from "../components/Slider" import FeedbackCard from "../components/FeedbackCard" +import Quiz from "../components/Quiz/Quiz" import { getLocaleForNumberFormat, @@ -52,6 +53,9 @@ import { GATSBY_FUNCTIONS_PATH } from "../constants" import { Context } from "../types" import StatErrorMessage from "../components/StatErrorMessage" import StatLoadingMessage from "../components/StatLoadingMessage" +import { Center } from "@chakra-ui/react" + +import quizzes from "../data/learnQuzzes/index" const Slogan = styled.p` font-style: normal; @@ -942,6 +946,18 @@ const WhatIsEthereumPage = ({ + + +
+

+ +

+
+
+ +
+
+ From f8bcc47db20dd7b0393652f7cbf06daba4bad234 Mon Sep 17 00:00:00 2001 From: Corwin Smith Date: Sat, 17 Sep 2022 16:50:31 +0200 Subject: [PATCH 02/56] resolve quiz based on url, or with quizKey --- src/components/Quiz/Quiz.tsx | 50 +++++++++++++++------- src/pages-conditional/what-is-ethereum.tsx | 4 +- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/components/Quiz/Quiz.tsx b/src/components/Quiz/Quiz.tsx index 62572e5db75..f5bb31611a8 100644 --- a/src/components/Quiz/Quiz.tsx +++ b/src/components/Quiz/Quiz.tsx @@ -1,28 +1,45 @@ // Libraries -import React, { useState } from "react" +import React, { useEffect, useState } from "react" import { Box, Center, Text, useColorMode } from "@chakra-ui/react" // Components import QuizQuestion from "./QuizQuestion" +// Data +import quizzes from "../../data/learnQuzzes/index" + // Types export interface IProps { - quiz: any + quizKey?: string | undefined } -const Quiz: React.FC = ({ quiz }) => { +const Quiz: React.FC = ({ quizKey }) => { const { colorMode } = useColorMode() const [currentQuestionIndex, updateCurrentQuestionIndex] = useState(0) - const [quizData, setQuizdata] = useState( - quiz.questions.map((question) => { - return { - ...question, - userAnswer: undefined, - } - }) - ) + const [quizData, setQuizData] = useState(undefined) + + useEffect(() => { + if (typeof window !== "undefined") { + const baseQuizKey = + quizKey || + Object.keys(quizzes).filter((quizUri) => + window.location.href.includes(quizUri) + )[0] + setQuizData({ + ...quizzes[baseQuizKey], + questions: quizzes[baseQuizKey].questions.map((question) => { + return { + ...question, + userAnswer: undefined, + } + }), + }) + } + }, []) - return ( + console.log(quizData) + + return !quizData ? null : ( = ({ quiz }) => { bg={colorMode === "dark" ? "gray.900" : "white"} borderRadius={"4px"} boxShadow={"0px 9px 16px -6px rgba(0, 0, 0, 0.13)"} - padding={"49px 62px"} + padding={{ + md: "49px 62px", + base: "20px 30px", + }} >
= ({ quiz }) => { fontWeight={"700"} color={colorMode === "dark" ? "orange.300" : "blue.300"} > - {quiz.title} + {quizData.title}
- +
) diff --git a/src/pages-conditional/what-is-ethereum.tsx b/src/pages-conditional/what-is-ethereum.tsx index 55322f72a2f..dd0a92dcf11 100644 --- a/src/pages-conditional/what-is-ethereum.tsx +++ b/src/pages-conditional/what-is-ethereum.tsx @@ -55,8 +55,6 @@ import StatErrorMessage from "../components/StatErrorMessage" import StatLoadingMessage from "../components/StatLoadingMessage" import { Center } from "@chakra-ui/react" -import quizzes from "../data/learnQuzzes/index" - const Slogan = styled.p` font-style: normal; font-weight: normal; @@ -954,7 +952,7 @@ const WhatIsEthereumPage = ({
- +
From 7a711baba705aa8b2c32b2a88af9bd672c6dc164 Mon Sep 17 00:00:00 2001 From: Corwin Smith Date: Thu, 22 Sep 2022 14:57:03 -0600 Subject: [PATCH 03/56] temp --- .../gatsby-plugin/components/Button.ts | 25 +++++++++ .../gatsby-plugin/semanticTokens.ts | 1 + src/components/Quiz/Quiz.tsx | 46 +++++++++++++-- src/components/Quiz/QuizQuestion.tsx | 56 ++++++++++++++----- src/data/learnQuzzes/whatIsEthereumQuiz.ts | 22 ++++++++ 5 files changed, 131 insertions(+), 19 deletions(-) diff --git a/src/@chakra-ui/gatsby-plugin/components/Button.ts b/src/@chakra-ui/gatsby-plugin/components/Button.ts index 17f5c9ee98d..473bc938b6b 100644 --- a/src/@chakra-ui/gatsby-plugin/components/Button.ts +++ b/src/@chakra-ui/gatsby-plugin/components/Button.ts @@ -26,6 +26,28 @@ const commonOutline = { }, } +const quizButton = { + width: "100%", + whiteSpace: "inherit", + justifyContent: "start", + marginBottom: "16px", + bg: "quizButton", + border: "1px", + borderColor: "transparent", + _hover: { + borderColor: "primary", + boxSizing: "border-box", + }, + _focus: { + borderColor: "transparent", + boxShadow: "none", + }, + _active: { + bg: "primary", + color: "buttonColor", + }, +} + export const Button: ComponentStyleConfig = { baseStyle: { fontWeight: "normal", @@ -70,5 +92,8 @@ export const Button: ComponentStyleConfig = { color: "primary", borderColor: "primary", }, + quizButton: { + ...quizButton, + }, }, } diff --git a/src/@chakra-ui/gatsby-plugin/semanticTokens.ts b/src/@chakra-ui/gatsby-plugin/semanticTokens.ts index 7adbb3436e7..7be88b03847 100644 --- a/src/@chakra-ui/gatsby-plugin/semanticTokens.ts +++ b/src/@chakra-ui/gatsby-plugin/semanticTokens.ts @@ -38,6 +38,7 @@ const semanticTokens = { bodyLight: { _light: "gray.500", _dark: "gray.100" }, disabled: { _light: "gray.400", _dark: "gray.500" }, background: { _light: "white", _dark: "gray.700" }, + quizButton: { _light: "gray.100", _dark: "gray.700" }, success: "green.500", error: "red.500", attention: "yellow.200", diff --git a/src/components/Quiz/Quiz.tsx b/src/components/Quiz/Quiz.tsx index f5bb31611a8..e4c81ee23ec 100644 --- a/src/components/Quiz/Quiz.tsx +++ b/src/components/Quiz/Quiz.tsx @@ -1,8 +1,18 @@ // Libraries import React, { useEffect, useState } from "react" -import { Box, Center, Text, useColorMode } from "@chakra-ui/react" +import { + Box, + ButtonGroup, + Center, + Container, + Grid, + GridItem, + Text, + useColorMode, +} from "@chakra-ui/react" // Components +import Button from "../Button" import QuizQuestion from "./QuizQuestion" // Data @@ -15,7 +25,7 @@ export interface IProps { const Quiz: React.FC = ({ quizKey }) => { const { colorMode } = useColorMode() - const [currentQuestionIndex, updateCurrentQuestionIndex] = useState(0) + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(1) const [quizData, setQuizData] = useState(undefined) useEffect(() => { @@ -37,8 +47,6 @@ const Quiz: React.FC = ({ quizKey }) => { } }, []) - console.log(quizData) - return !quizData ? null : ( = ({ quizKey }) => { {quizData.title} +
+ {quizData.questions.map((question) => { + console.log(question) + return ( + + ) + })} +
+
+ + {currentQuestionIndex > 0 ? ( + + ) : null} + + +
) } diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index 25f304d2173..f130b3d939d 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -1,6 +1,9 @@ // Libraries -import React from "react" -import { Box, Text, useColorMode } from "@chakra-ui/react" +import React, { useState } from "react" +import { Box, Circle, Text, useColorMode } from "@chakra-ui/react" + +// Components +import Button from "../Button" // Types export interface IProps { @@ -10,26 +13,49 @@ export interface IProps { const QuizQuestion: React.FC = ({ questionData }) => { const { colorMode } = useColorMode() const { answers, question } = questionData + const [selectedAnswer, setSelectedAnswer] = useState( + undefined + ) + return ( - + {question} {Object.keys(answers).map((key) => { + const active = selectedAnswer === key + const iconBackgroundDark = active ? "orange.800" : "gray.500" + const iconBackgroundLight = active ? "blue.300" : "gray.400" + return ( - { + setSelectedAnswer(key) + }} + leftIcon={ + + + {key.toUpperCase()} + + + } > - - {key} - - - {answers[key].label} - - + {answers[key].label} + ) })} diff --git a/src/data/learnQuzzes/whatIsEthereumQuiz.ts b/src/data/learnQuzzes/whatIsEthereumQuiz.ts index d5ccb87e745..8457dcd3b64 100644 --- a/src/data/learnQuzzes/whatIsEthereumQuiz.ts +++ b/src/data/learnQuzzes/whatIsEthereumQuiz.ts @@ -29,6 +29,28 @@ const whatIsEthereumQuiz = { }, }, }, + { + correctAnswer: "a", + question: "Blah Blah", + answers: { + a: { + label: "a", + description: "a", + }, + b: { + label: "b", + description: "b", + }, + c: { + label: "c", + description: "c", + }, + d: { + label: "d", + description: "d", + }, + }, + }, ], } From 4880b35a01d43be6183da24911d6557c6e0b55b8 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Sat, 8 Oct 2022 16:28:54 -0500 Subject: [PATCH 04/56] Add quiz data types --- src/types.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/types.ts b/src/types.ts index 27c27b47978..a1e9680cebd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,3 +53,49 @@ type OptionalImageProp = { type ForbidOptionalImageProp = ForbidOptional export type ImageProp = OptionalImageProp | ForbidOptionalImageProp + +/** + * Quiz data types + */ +export interface AnswerChoice { + answerId: string + isCorrect: boolean +} + +export interface Answer { + id: string + label: string + explanation: string + moreInfo?: string + moreInfoUrl?: string +} + +export interface RawQuestion { + prompt: string + answers: Array + correctAnswerId: string // Answer.id +} + +export interface Question extends RawQuestion { + id: string // 0a001 || Hash of prompt?? +} + +export interface QuestionBank { + [key: string]: RawQuestion +} + +export interface RawQuiz { + // id: string // Use "key" from Quizzes as ID instead + title: string + questions: Array // TODO: Force to be an array of questionID's +} + +export interface Quiz { + title: string + questions: Array +} + +export interface RawQuizzes { + [key: string]: RawQuiz +} + From 27924375bf90a81722d073ff8b8648bc42380878 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Sat, 8 Oct 2022 16:37:49 -0500 Subject: [PATCH 05/56] refactor quiz-data data structure --- src/data/learnQuizzes/index.ts | 16 +++++ src/data/learnQuizzes/questionBank.ts | 71 ++++++++++++++++++++++ src/data/learnQuzzes/index.ts | 8 --- src/data/learnQuzzes/whatIsEthereumQuiz.ts | 57 ----------------- 4 files changed, 87 insertions(+), 65 deletions(-) create mode 100644 src/data/learnQuizzes/index.ts create mode 100644 src/data/learnQuizzes/questionBank.ts delete mode 100644 src/data/learnQuzzes/index.ts delete mode 100644 src/data/learnQuzzes/whatIsEthereumQuiz.ts diff --git a/src/data/learnQuizzes/index.ts b/src/data/learnQuizzes/index.ts new file mode 100644 index 00000000000..8ac7a4a77b4 --- /dev/null +++ b/src/data/learnQuizzes/index.ts @@ -0,0 +1,16 @@ +// Import data types +import { RawQuizzes } from "../../types" + +// Declare hash-map of quizzes based on slug key +const quizzes: RawQuizzes = { + "what-is-ethereum": { + title: "What is Ethereum?", + questions: ["a001", "a002"], + }, + "what-is-ether": { + title: "What is ether?", + questions: [], + }, +} + +export default quizzes diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts new file mode 100644 index 00000000000..94a30ca9e39 --- /dev/null +++ b/src/data/learnQuizzes/questionBank.ts @@ -0,0 +1,71 @@ +// Import data types +import { QuestionBank } from "../../types" + +// Declare hash map of question bank +const questionBank: QuestionBank = { + // TODO: Use hash of English prompt to generate this key + a001: { + prompt: "What is the biggest difference between Bitcoin and Ethereum?", + answers: [ + { + id: "a001-a", // TODO: Use hash of English question label to generate this id + label: + "Bitcoin has middlemen that mediate transactions, Ethereum does not", + explanation: + "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + }, + { + id: "a001-b", + label: "Ethereum is programmable, Bitcoin is not", + explanation: + "Unlike Bitcoin, Ethereum has smart contracts, expanding the utility of the network past payments.", + }, + { + id: "a001-c", + label: + "Ethereum has middlemen that mediate transactions, Bitcoin does not", + explanation: + "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + }, + { + id: "a001-d", + label: "Ethereum preserves privacy, Bitcoin does not", + explanation: + "Every node needs to be able to verify the blockchain's history from the beginning until the present. Therefore, there are no secrets on the blockchain. The only way to preserve privacy on the blockchain is to use private keys (and therefore identities) that cannot be traced back to you. This is possible on both Bitcoin and Ethereum.", + }, + ], + correctAnswerId: "a001-b", + }, + a002: { + prompt: "Ethereum consumes less electricity than:", + answers: [ + { + id: "a002-a", + label: "YouTube", + explanation: + "YouTube consumes approximately 244 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + }, + { + id: "a002-b", + label: "Netflix", + explanation: + "Netflix consumes approximately 94 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + }, + { + id: "a002-c", + label: "PayPal", + explanation: + "PayPal consumes approximately 0.26 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + }, + { + id: "a002-d", + label: "Bitcoin", + explanation: + "Bitcoin consumes approximately 200 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + }, + ], + correctAnswerId: "a002-c", + }, +} + +export default questionBank diff --git a/src/data/learnQuzzes/index.ts b/src/data/learnQuzzes/index.ts deleted file mode 100644 index 853ef5b152f..00000000000 --- a/src/data/learnQuzzes/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Quizzes -import whatIsEthereumQuiz from "./whatIsEthereumQuiz" - -const quizzes = { - "what-is-ethereum": whatIsEthereumQuiz, -} - -export default quizzes diff --git a/src/data/learnQuzzes/whatIsEthereumQuiz.ts b/src/data/learnQuzzes/whatIsEthereumQuiz.ts deleted file mode 100644 index 8457dcd3b64..00000000000 --- a/src/data/learnQuzzes/whatIsEthereumQuiz.ts +++ /dev/null @@ -1,57 +0,0 @@ -const whatIsEthereumQuiz = { - title: "What is Ethereum Quiz", - questions: [ - { - correctAnswer: "b", - question: "What is the biggest difference between Bitcoin and Ethereum?", - answers: { - a: { - label: - "Bitcoin has middlemen that mediate transactions, Ethereum does not", - description: - "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", - }, - b: { - label: "Ethereum is programmable, Bitcoin is not", - description: - "Unlike Bitcoin, Ethereum has smart contracts, expanding the utility of the network past payments.", - }, - c: { - label: - "Ethereum has middlemen that mediate transactions, Bitcoin does not", - description: - "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", - }, - d: { - label: "Ethereum preserves privacy, Bitcoin does not", - description: - "Every node needs to be able to verify the blockchain's history from the beginning until the present. Therefore, there are no secrets on the blockchain. The only way to preserve privacy on the blockchain is to use private keys (and therefore identities) that cannot be traced back to you. This is possible on both Bitcoin and Ethereum.", - }, - }, - }, - { - correctAnswer: "a", - question: "Blah Blah", - answers: { - a: { - label: "a", - description: "a", - }, - b: { - label: "b", - description: "b", - }, - c: { - label: "c", - description: "c", - }, - d: { - label: "d", - description: "d", - }, - }, - }, - ], -} - -export default whatIsEthereumQuiz From 815ea35c1bc1a4990835fdc331b445b350232143 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Sat, 8 Oct 2022 16:50:57 -0500 Subject: [PATCH 06/56] update QuizQuestion to accept callback function --- src/components/Quiz/QuizQuestion.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index f130b3d939d..ea7b48d51fb 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -8,15 +8,20 @@ import Button from "../Button" // Types export interface IProps { questionData: any + onAnswerSelect: (answerId: string) => void } -const QuizQuestion: React.FC = ({ questionData }) => { +const QuizQuestion: React.FC = ({ questionData, onAnswerSelect }) => { const { colorMode } = useColorMode() const { answers, question } = questionData const [selectedAnswer, setSelectedAnswer] = useState( undefined ) + const handleSelection = (answerId: string) => { + setSelectedAnswer(answerId) + onAnswerSelect(answerId) + } return ( @@ -31,9 +36,8 @@ const QuizQuestion: React.FC = ({ questionData }) => { ) : null} - + From 386c6fd494e8f10ed9dadbc5ca2415b664d087fa Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:38:18 -0500 Subject: [PATCH 08/56] update types --- src/types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index a1e9680cebd..3c9d2d7db09 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,7 +66,7 @@ export interface Answer { id: string label: string explanation: string - moreInfo?: string + moreInfoLabel?: string moreInfoUrl?: string } @@ -98,4 +98,3 @@ export interface Quiz { export interface RawQuizzes { [key: string]: RawQuiz } - From 179d5408c6d286ec44a515dcaebc2df415888763 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:38:53 -0500 Subject: [PATCH 09/56] update QuizWidget logic renamed from Quiz to limit name collision --- src/components/Quiz/Quiz.tsx | 161 --------------------- src/components/Quiz/QuizWidget.tsx | 224 +++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 161 deletions(-) delete mode 100644 src/components/Quiz/Quiz.tsx create mode 100644 src/components/Quiz/QuizWidget.tsx diff --git a/src/components/Quiz/Quiz.tsx b/src/components/Quiz/Quiz.tsx deleted file mode 100644 index 0376cd6de31..00000000000 --- a/src/components/Quiz/Quiz.tsx +++ /dev/null @@ -1,161 +0,0 @@ -// Libraries -import React, { useEffect, useState, useMemo } from "react" -import { - Box, - ButtonGroup, - Center, - Container, - Text, - useColorMode, -} from "@chakra-ui/react" -import { shuffle } from "lodash" - -// Components -import Button from "../Button" -import QuizQuestion from "./QuizQuestion" - -// Data -import allQuizData from "../../data/learnQuizzes" -import questionBank from "../../data/learnQuizzes/questionBank" - -// Type -import { AnswerChoice, RawQuiz, Quiz, RawQuestion, Question } from "../../types" - -export interface IProps { - quizKey?: string - maxQuestions?: number -} - -const Quiz: React.FC = ({ quizKey, maxQuestions }) => { - const { colorMode } = useColorMode() - const isDarkMode = colorMode === "dark" - - const [quizData, setQuizData] = useState(null) - const [currentQuestionAnswerChoice, setCurrentQuestionAnswerChoice] = - useState(null) - const [userQuizProgress, setUserQuizProgress] = useState>( - [] - ) - - useEffect(() => { - // Reset state - setQuizData(null) - setCurrentQuestionAnswerChoice(null) - setUserQuizProgress([]) - - // Get quiz key - const currentQuizKey = - quizKey || - Object.keys(allQuizData).filter((quizUri) => - window?.location.href.includes(quizUri) - )[0] || - null - - // Get quiz data, shuffle, then truncate if necessary - if (currentQuizKey) { - const rawQuiz: RawQuiz = allQuizData[currentQuizKey] - const questions: Array = rawQuiz.questions.map((id) => { - const rawQuestion: RawQuestion = questionBank[id] - return { id, ...rawQuestion } - }) - const shuffledQuestions = shuffle(questions) - const trimmedQuestions = maxQuestions - ? shuffledQuestions.slice(0, maxQuestions) - : shuffledQuestions - const quiz: Quiz = { title: rawQuiz.title, questions: trimmedQuestions } - setQuizData(quiz) - } else { - setQuizData(null) - } - }, [quizKey, window.location.href]) - - // Memos - const currentQuestionIndex = useMemo( - () => userQuizProgress.length || 0, - [userQuizProgress] - ) - const showResults = useMemo( - () => userQuizProgress.length !== quizData?.questions.length, - [userQuizProgress, quizData] - ) - // TODO: if (showResults) { render summary view } - - // Handlers - const handleSelectAnswerChoice = (answerId: string) => { - const isCorrect = - answerId === quizData?.questions[currentQuestionIndex].correctAnswerId - setCurrentQuestionAnswerChoice({ answerId, isCorrect }) - } - const handleRetryQuestion = () => { - setCurrentQuestionAnswerChoice(null) - // Setting this to `null` should reset the question - } - const handleContinue = () => { - if (!currentQuestionAnswerChoice) return - setUserQuizProgress((prev) => [...prev, currentQuestionAnswerChoice]) - } - - const handleSubmit = () => { - // TODO: Allow user to submit quiz for storage - } - - return !quizData ? null : ( - -
- - {quizData.title} - -
-
- {quizData.questions.map(() => ( - - ))} -
-
- -
-
- - {currentQuestionIndex > 0 ? ( - - ) : null} - - -
-
- ) -} - -export default Quiz diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx new file mode 100644 index 00000000000..3a41d680c5b --- /dev/null +++ b/src/components/Quiz/QuizWidget.tsx @@ -0,0 +1,224 @@ +// Libraries +import React, { useEffect, useState, useMemo } from "react" +import { + Box, + ButtonGroup, + Center, + Container, + Flex, + Text, + useColorMode, +} from "@chakra-ui/react" +import { shuffle } from "lodash" + +// Components +import Button from "../Button" +import QuizQuestion from "./QuizQuestion" +import Translation from "../Translation" + +// Data +import allQuizData from "../../data/learnQuizzes" +import questionBank from "../../data/learnQuizzes/questionBank" + +// Type +import { AnswerChoice, RawQuiz, Quiz, RawQuestion, Question } from "../../types" + +export interface IProps { + quizKey?: string + maxQuestions?: number +} +const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { + // TODO: Add loading indictor + // TODO: Add error handling + // TODO: Add summary page once userQuizProgress.length === quizData.length + const { colorMode } = useColorMode() + const isDarkMode = colorMode === "dark" + + const [quizData, setQuizData] = useState(null) + const [currentQuestionAnswerChoice, setCurrentQuestionAnswerChoice] = + useState(null) + const [userQuizProgress, setUserQuizProgress] = useState>( + [] + ) + const [showAnswer, setShowAnswer] = useState(false) + const [selectedAnswer, setSelectedAnswer] = useState(null) + + useEffect(() => { + // Reset state + setQuizData(null) + setCurrentQuestionAnswerChoice(null) + setUserQuizProgress([]) + setShowAnswer(false) + + // Get quiz key + const currentQuizKey = + quizKey || + Object.keys(allQuizData).filter((quizUri) => + window?.location.href.includes(quizUri) + )[0] || + null + + // Get quiz data, shuffle, then truncate if necessary + if (currentQuizKey) { + const rawQuiz: RawQuiz = allQuizData[currentQuizKey] + const questions: Array = rawQuiz.questions.map((id) => { + const rawQuestion: RawQuestion = questionBank[id] + return { id, ...rawQuestion } + }) + const shuffledQuestions = shuffle(questions) + const trimmedQuestions = maxQuestions + ? shuffledQuestions.slice(0, maxQuestions) + : shuffledQuestions + const quiz: Quiz = { title: rawQuiz.title, questions: trimmedQuestions } + setQuizData(quiz) + } else { + setQuizData(null) + } + }, [quizKey]) + + // Memos + const currentQuestionIndex = useMemo( + () => userQuizProgress.length || 0, + [userQuizProgress] + ) + const showResults = useMemo( + () => userQuizProgress.length !== quizData?.questions.length, + [userQuizProgress, quizData] + ) + + const cardBackground = useMemo(() => { + if (showAnswer) { + if (currentQuestionAnswerChoice?.isCorrect) + return isDarkMode ? "#0A160E" : "#C8F7D8" + return isDarkMode ? "#1B0C0C" : "#F7C8C8" + } + return isDarkMode ? "gray.900" : "white" + }, [isDarkMode, showAnswer]) + + // Handlers + const handleSelectAnswerChoice = (answerId: string) => { + const isCorrect = + answerId === quizData?.questions[currentQuestionIndex].correctAnswerId + setCurrentQuestionAnswerChoice({ answerId, isCorrect }) + } + // TODO: Confirm both handleSelectAnswerChoice & handleSelection are necessary + const handleSelection = (answerId: string) => { + setSelectedAnswer(answerId) + handleSelectAnswerChoice(answerId) + } + + const handleShowAnswer = () => { + setShowAnswer(true) + } + const handleRetryQuestion = () => { + // TODO: Tell QuizQuestion component to reset + setCurrentQuestionAnswerChoice(null) // Setting to `null` should reset the question + setSelectedAnswer(null) // Being passed to child component + setShowAnswer(false) + } + const handleContinue = () => { + if (!currentQuestionAnswerChoice) return + setUserQuizProgress((prev) => [...prev, currentQuestionAnswerChoice]) + setShowAnswer(false) + } + + const handleSubmit = () => { + // TODO: Allow user to submit quiz for storage + } + + return !quizData ? null : ( + +

+ +

+ +
+ + {quizData.title} + +
+
+ {quizData.questions.map(({ id }, index) => { + let bg: string + if ( + (showAnswer && + index === currentQuestionIndex && + currentQuestionAnswerChoice?.isCorrect) || + userQuizProgress[index]?.isCorrect + ) { + bg = "#48BB78" + } else if ( + (showAnswer && + index === currentQuestionIndex && + !currentQuestionAnswerChoice?.isCorrect) || + (userQuizProgress[index] && !userQuizProgress[index].isCorrect) + ) { + bg = "#B80000" + } else if (index === currentQuestionIndex) { + bg = "#B0B0B0" + } else { + bg = "#646464" + } + return ( + + ) + })} +
+
+ +
+
+ + {showAnswer && + currentQuestionAnswerChoice && + !currentQuestionAnswerChoice.isCorrect && ( + + )} + {showAnswer ? ( + + ) : ( + + )} + +
+
+
+ ) +} + +export default QuizWidget From fe4ddc532e010b07986ddb1cf16895cdc17150a9 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:39:21 -0500 Subject: [PATCH 10/56] update QuizQuestion --- src/components/Quiz/QuizQuestion.tsx | 54 ++++++++++++++-------- src/data/learnQuizzes/questionBank.ts | 4 +- src/pages-conditional/what-is-ethereum.tsx | 9 +--- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index ea7b48d51fb..d735ac6edb1 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -1,42 +1,50 @@ // Libraries -import React, { useState } from "react" +import React, { useState, useMemo } from "react" import { Box, Circle, Text, useColorMode } from "@chakra-ui/react" // Components import Button from "../Button" // Types +import { Question } from "../../types" + export interface IProps { - questionData: any - onAnswerSelect: (answerId: string) => void + questionData: Question + showAnswer: boolean + handleSelection: (answerId: string) => void + selectedAnswer: string | null } -const QuizQuestion: React.FC = ({ questionData, onAnswerSelect }) => { +const QuizQuestion: React.FC = ({ + questionData, + showAnswer, + handleSelection, + selectedAnswer, +}) => { const { colorMode } = useColorMode() - const { answers, question } = questionData - const [selectedAnswer, setSelectedAnswer] = useState( - undefined - ) + const { answers, prompt } = questionData + const explanation = useMemo(() => { + if (!selectedAnswer) return "" + return answers.filter(({ id }) => id === selectedAnswer)[0].explanation + }, [selectedAnswer]) - const handleSelection = (answerId: string) => { - setSelectedAnswer(answerId) - onAnswerSelect(answerId) - } return ( - {question} + {prompt} - {Object.keys(answers).map((key) => { - const active = selectedAnswer === key + {answers.map(({ id, label }, index) => { + const active = selectedAnswer === id const iconBackgroundDark = active ? "orange.800" : "gray.500" const iconBackgroundLight = active ? "blue.300" : "gray.400" - + const display = + !showAnswer || id === selectedAnswer ? "inline-flex" : "none" return ( ) })} + {showAnswer && ( + <> + Explanation + {explanation} + + )} ) } diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts index 94a30ca9e39..a728c3722b5 100644 --- a/src/data/learnQuizzes/questionBank.ts +++ b/src/data/learnQuizzes/questionBank.ts @@ -59,9 +59,9 @@ const questionBank: QuestionBank = { }, { id: "a002-d", - label: "Bitcoin", + label: "All of the above", explanation: - "Bitcoin consumes approximately 200 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + "Ethereum consumes approximately 0.01 TW/year, while YouTube consumes ~244 TW/year, Netflix ~94 TW/year, and PayPal ~0.26 TW/year.", }, ], correctAnswerId: "a002-c", diff --git a/src/pages-conditional/what-is-ethereum.tsx b/src/pages-conditional/what-is-ethereum.tsx index dd0a92dcf11..4286f3a4dc7 100644 --- a/src/pages-conditional/what-is-ethereum.tsx +++ b/src/pages-conditional/what-is-ethereum.tsx @@ -35,7 +35,7 @@ import AdoptionChart from "../components/AdoptionChart" import EnergyConsumptionChart from "../components/EnergyConsumptionChart" import Slider, { EmblaSlide } from "../components/Slider" import FeedbackCard from "../components/FeedbackCard" -import Quiz from "../components/Quiz/Quiz" +import QuizWidget from "../components/Quiz/QuizWidget" import { getLocaleForNumberFormat, @@ -947,12 +947,7 @@ const WhatIsEthereumPage = ({
-

- -

-
-
- +
From 4dbb1e671e9c6ea939e2ec479df9af4517689d7a Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:16:48 -0500 Subject: [PATCH 11/56] feat: QuizWidget mvp --- src/components/Quiz/QuizQuestion.tsx | 2 +- src/components/Quiz/QuizSummary.tsx | 50 ++++++ src/components/Quiz/QuizWidget.tsx | 237 +++++++++++++++----------- src/data/learnQuizzes/questionBank.ts | 2 +- 4 files changed, 186 insertions(+), 105 deletions(-) create mode 100644 src/components/Quiz/QuizSummary.tsx diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index d735ac6edb1..2d2d7bbcabc 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { useState, useMemo } from "react" +import React, { useMemo } from "react" import { Box, Circle, Text, useColorMode } from "@chakra-ui/react" // Components diff --git a/src/components/Quiz/QuizSummary.tsx b/src/components/Quiz/QuizSummary.tsx new file mode 100644 index 00000000000..e343f651e92 --- /dev/null +++ b/src/components/Quiz/QuizSummary.tsx @@ -0,0 +1,50 @@ +// Libraries +import React, { useMemo } from "react" +import { Box, Flex, Text } from "@chakra-ui/react" + +export interface IProps { + correctCount: number + questionCount: number +} + +const QuizSummary: React.FC = ({ correctCount, questionCount }) => { + const percentCorrect = useMemo( + () => Math.floor((correctCount / questionCount) * 100), + [correctCount, questionCount] + ) + + return ( + + + {percentCorrect >= 65 ? "You passed the quiz!" : "Your results"} + + + + + {percentCorrect}% + + + Score + + + + + +{correctCount} + + + Total points + + + + + ) +} + +export default QuizSummary diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 3a41d680c5b..e326c71c103 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -6,14 +6,17 @@ import { Center, Container, Flex, + Icon, Text, useColorMode, } from "@chakra-ui/react" import { shuffle } from "lodash" +import { FaTwitter } from "react-icons/fa" // Components import Button from "../Button" import QuizQuestion from "./QuizQuestion" +import QuizSummary from "./QuizSummary" import Translation from "../Translation" // Data @@ -28,9 +31,7 @@ export interface IProps { maxQuestions?: number } const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { - // TODO: Add loading indictor - // TODO: Add error handling - // TODO: Add summary page once userQuizProgress.length === quizData.length + // TODO: Add loading state indicator and error handling const { colorMode } = useColorMode() const isDarkMode = colorMode === "dark" @@ -43,12 +44,13 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { const [showAnswer, setShowAnswer] = useState(false) const [selectedAnswer, setSelectedAnswer] = useState(null) - useEffect(() => { + const initialize = () => { // Reset state setQuizData(null) setCurrentQuestionAnswerChoice(null) setUserQuizProgress([]) setShowAnswer(false) + setSelectedAnswer(null) // Get quiz key const currentQuizKey = @@ -74,15 +76,18 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { } else { setQuizData(null) } - }, [quizKey]) + } + useEffect(initialize, [quizKey]) - // Memos + // Memoized values const currentQuestionIndex = useMemo( () => userQuizProgress.length || 0, [userQuizProgress] ) + + // TODO: Allow user to submit quiz for storage const showResults = useMemo( - () => userQuizProgress.length !== quizData?.questions.length, + () => userQuizProgress.length === quizData?.questions.length, [userQuizProgress, quizData] ) @@ -95,12 +100,18 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { return isDarkMode ? "gray.900" : "white" }, [isDarkMode, showAnswer]) + const correctCount = useMemo( + () => userQuizProgress.filter(({ isCorrect }) => isCorrect).length, + [userQuizProgress] + ) + // Handlers const handleSelectAnswerChoice = (answerId: string) => { const isCorrect = answerId === quizData?.questions[currentQuestionIndex].correctAnswerId setCurrentQuestionAnswerChoice({ answerId, isCorrect }) } + // TODO: Confirm both handleSelectAnswerChoice & handleSelection are necessary const handleSelection = (answerId: string) => { setSelectedAnswer(answerId) @@ -110,114 +121,134 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { const handleShowAnswer = () => { setShowAnswer(true) } + const handleRetryQuestion = () => { - // TODO: Tell QuizQuestion component to reset - setCurrentQuestionAnswerChoice(null) // Setting to `null` should reset the question - setSelectedAnswer(null) // Being passed to child component + setCurrentQuestionAnswerChoice(null) + setSelectedAnswer(null) setShowAnswer(false) } const handleContinue = () => { if (!currentQuestionAnswerChoice) return setUserQuizProgress((prev) => [...prev, currentQuestionAnswerChoice]) + setCurrentQuestionAnswerChoice(null) setShowAnswer(false) } - const handleSubmit = () => { - // TODO: Allow user to submit quiz for storage - } - - return !quizData ? null : ( - -

- -

- -
- - {quizData.title} - -
-
- {quizData.questions.map(({ id }, index) => { - let bg: string - if ( - (showAnswer && - index === currentQuestionIndex && - currentQuestionAnswerChoice?.isCorrect) || - userQuizProgress[index]?.isCorrect - ) { - bg = "#48BB78" - } else if ( - (showAnswer && - index === currentQuestionIndex && - !currentQuestionAnswerChoice?.isCorrect) || - (userQuizProgress[index] && !userQuizProgress[index].isCorrect) - ) { - bg = "#B80000" - } else if (index === currentQuestionIndex) { - bg = "#B0B0B0" - } else { - bg = "#646464" - } - return ( - +

+ +

+ +
+ + {quizData.title} + +
+
+ {quizData.questions.map(({ id }, index) => { + let bg: string + if ( + (showAnswer && + index === currentQuestionIndex && + currentQuestionAnswerChoice?.isCorrect) || + userQuizProgress[index]?.isCorrect + ) { + bg = "#48BB78" + } else if ( + (showAnswer && + index === currentQuestionIndex && + !currentQuestionAnswerChoice?.isCorrect) || + (userQuizProgress[index] && !userQuizProgress[index].isCorrect) + ) { + bg = "#B80000" + } else if (index === currentQuestionIndex) { + bg = "#B0B0B0" + } else { + bg = "#646464" + } + return ( + + ) + })} +
+
+ {showResults ? ( + - ) - })} -
-
- -
-
- - {showAnswer && - currentQuestionAnswerChoice && - !currentQuestionAnswerChoice.isCorrect && ( - - )} - {showAnswer ? ( - ) : ( - + )} - -
-
- +
+
+ + {showAnswer && + currentQuestionAnswerChoice && + !currentQuestionAnswerChoice.isCorrect && ( + + )} + {showResults ? ( + + + + + ) : showAnswer ? ( + + ) : ( + + )} + +
+
+
+ ) ) } diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts index a728c3722b5..8d4ec3591b9 100644 --- a/src/data/learnQuizzes/questionBank.ts +++ b/src/data/learnQuizzes/questionBank.ts @@ -64,7 +64,7 @@ const questionBank: QuestionBank = { "Ethereum consumes approximately 0.01 TW/year, while YouTube consumes ~244 TW/year, Netflix ~94 TW/year, and PayPal ~0.26 TW/year.", }, ], - correctAnswerId: "a002-c", + correctAnswerId: "a002-d", }, } From a2251f9a435c235cae84e7055d35b089d6ab03c8 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:02:10 -0500 Subject: [PATCH 12/56] add trophy/status svg indicators --- src/assets/quiz/correct.svg | 3 ++ src/assets/quiz/incorrect.svg | 3 ++ src/assets/quiz/star-confetti.svg | 25 ++++++++++++ src/assets/quiz/trophy.svg | 4 ++ src/assets/trophy.svg | 11 ----- src/components/Quiz/QuizQuestion.tsx | 4 +- src/components/Quiz/QuizWidget.tsx | 60 +++++++++++++++++++++++++++- 7 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 src/assets/quiz/correct.svg create mode 100644 src/assets/quiz/incorrect.svg create mode 100644 src/assets/quiz/star-confetti.svg create mode 100644 src/assets/quiz/trophy.svg delete mode 100644 src/assets/trophy.svg diff --git a/src/assets/quiz/correct.svg b/src/assets/quiz/correct.svg new file mode 100644 index 00000000000..7df1f6a3844 --- /dev/null +++ b/src/assets/quiz/correct.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/quiz/incorrect.svg b/src/assets/quiz/incorrect.svg new file mode 100644 index 00000000000..f391b34f9a6 --- /dev/null +++ b/src/assets/quiz/incorrect.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/quiz/star-confetti.svg b/src/assets/quiz/star-confetti.svg new file mode 100644 index 00000000000..74b302533f8 --- /dev/null +++ b/src/assets/quiz/star-confetti.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/quiz/trophy.svg b/src/assets/quiz/trophy.svg new file mode 100644 index 00000000000..c3b4f445311 --- /dev/null +++ b/src/assets/quiz/trophy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/trophy.svg b/src/assets/trophy.svg deleted file mode 100644 index a571d7f990a..00000000000 --- a/src/assets/trophy.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index 2d2d7bbcabc..252bac9a962 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -46,6 +46,8 @@ const QuizQuestion: React.FC = ({ isActive={active} onClick={() => handleSelection(id)} textAlign="start" + h="fit-content" + p={2} leftIcon={ = ({ } - h="fit-content" - p={2} > {label} diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index e326c71c103..03389634bb0 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -4,8 +4,10 @@ import { Box, ButtonGroup, Center, + Circle, Container, Flex, + Heading, Icon, Text, useColorMode, @@ -19,6 +21,12 @@ import QuizQuestion from "./QuizQuestion" import QuizSummary from "./QuizSummary" import Translation from "../Translation" +// SVG import +import Trophy from "../../assets/quiz/trophy.svg" +import Correct from "../../assets/quiz/correct.svg" +import Incorrect from "../../assets/quiz/incorrect.svg" +import StarConfetti from "../../assets/quiz/star-confetti.svg" + // Data import allQuizData from "../../data/learnQuizzes" import questionBank from "../../data/learnQuizzes/questionBank" @@ -137,9 +145,9 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { return ( quizData && ( -

+ -

+ = ({ quizKey, maxQuestions }) => { md: "49px 62px", // TODO: Remove magic numbers base: "20px 30px", }} + position="relative" > + + + + {showResults && + Math.floor((correctCount / quizData.questions.length) * 100) > + 65 && ( + <> + + + + )}
Date: Tue, 11 Oct 2022 08:34:13 -0500 Subject: [PATCH 13/56] update question bank and quizzes --- src/data/learnQuizzes/index.ts | 20 +- src/data/learnQuizzes/questionBank.ts | 723 +++++++++++++++++++++++++- 2 files changed, 721 insertions(+), 22 deletions(-) diff --git a/src/data/learnQuizzes/index.ts b/src/data/learnQuizzes/index.ts index 8ac7a4a77b4..818ec7bdc70 100644 --- a/src/data/learnQuizzes/index.ts +++ b/src/data/learnQuizzes/index.ts @@ -5,11 +5,27 @@ import { RawQuizzes } from "../../types" const quizzes: RawQuizzes = { "what-is-ethereum": { title: "What is Ethereum?", - questions: ["a001", "a002"], + questions: ["a001", "a002", "a003", "a004", "a005"], }, "what-is-ether": { title: "What is ether?", - questions: [], + questions: ["b001", "b002", "b003", "b004"], + }, + web3: { + title: "Web3", + questions: ["c001", "c002", "c003", "c004", "c005"], + }, + wallets: { + title: "Wallets", + questions: ["d001", "d002", "d003", "d004"], + }, + security: { + title: "Security", + questions: ["e001", "e002", "e003", "e004", "d003"], + }, + nfts: { + title: "NFTs", + questions: ["f001", "f002", "f003", "f004", "f005"], }, } diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts index 8d4ec3591b9..08f5c46aedb 100644 --- a/src/data/learnQuizzes/questionBank.ts +++ b/src/data/learnQuizzes/questionBank.ts @@ -4,67 +4,750 @@ import { QuestionBank } from "../../types" // Declare hash map of question bank const questionBank: QuestionBank = { // TODO: Use hash of English prompt to generate this key + // What is Ethereum? a001: { - prompt: "What is the biggest difference between Bitcoin and Ethereum?", + prompt: "The biggest difference between Ethereum and Bitcoin is:", answers: [ { id: "a001-a", // TODO: Use hash of English question label to generate this id - label: - "Bitcoin has middlemen that mediate transactions, Ethereum does not", + label: "Ethereum doesn’t let you make payments to other people", explanation: - "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + "Both Bitcoin and Ethereum let you make payments to other people.", }, { id: "a001-b", - label: "Ethereum is programmable, Bitcoin is not", + label: "You can run computer programs on Ethereum", explanation: - "Unlike Bitcoin, Ethereum has smart contracts, expanding the utility of the network past payments.", + "Ethereum is programmable. This means you can put any computer program on the Ethereum blockchain.", }, { id: "a001-c", - label: - "Ethereum has middlemen that mediate transactions, Bitcoin does not", + label: "You can run computer programs on Bitcoin", explanation: - "The whole point of cryptocurrency, any cryptocurrency, is that you can transact directly with whoever you wish, without the need for middlemen", + "Unlike Ethereum, Bitcoin isn’t programmable and cannot run arbitrary computer programs.", }, { id: "a001-d", - label: "Ethereum preserves privacy, Bitcoin does not", + label: "They have different logos", explanation: - "Every node needs to be able to verify the blockchain's history from the beginning until the present. Therefore, there are no secrets on the blockchain. The only way to preserve privacy on the blockchain is to use private keys (and therefore identities) that cannot be traced back to you. This is possible on both Bitcoin and Ethereum.", + "They do have different logos! But this isn’t the biggest difference between them.", }, ], correctAnswerId: "a001-b", }, a002: { - prompt: "Ethereum consumes less electricity than:", + prompt: "Ethereum’s native cryptocurrency is called:", answers: [ { id: "a002-a", - label: "YouTube", + label: "Ether", explanation: - "YouTube consumes approximately 244 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + "Ether is the cryptocurrency native to the Ethereum network.", }, { id: "a002-b", - label: "Netflix", + label: "Ethereum", explanation: - "Netflix consumes approximately 94 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + "Ethereum is the blockchain, but its native currency is not called Ethereum. This is a common misconception.", }, { id: "a002-c", - label: "PayPal", + label: "Ethercoin", explanation: - "PayPal consumes approximately 0.26 TW/year, while Ethereum consumes approximately 0.01 TW/year.", + "Unlike many other cryptocurrencies, Ethereum’s native cryptocurrency doesn’t contain the word ‘coin’.", }, { id: "a002-d", + label: "Bitcoin", + explanation: + "Bitcoin (uppercase B) was the first blockchain created, bitcoin (lowercase B) is it’s native cryptocurrency.", + }, + ], + correctAnswerId: "a002-a", + }, + a003: { + prompt: "Who runs Ethereum?", + answers: [ + { + id: "a003-a", + label: "Stakers", + explanation: + "Stakers are an important piece in the running of Ethereum, but stakers are duty-bound by the node operators to behave honestly.", + }, + { + id: "a003-b", + label: "Miners", + explanation: + "Mining hasn’t been possible since The Merge. There are no longer ‘miners’ on Ethereum.", + }, + { + id: "a003-c", + label: "The Ethereum Foundation", + explanation: + "The Ethereum Foundation does not play any significant role in the day-to-day running of Ethereum nodes. ", + }, + { + id: "a003-d", + label: "Anyone running a node", + explanation: + "Anyone running a node is a crucial part of Ethereum’s infrastructure. If you haven’t already, consider running an Ethereum node.", + }, + ], + correctAnswerId: "a003-d", + }, + a004: { + prompt: "What is Ethereum’s uptime?", + answers: [ + { + id: "a004-a", + label: "95%", + explanation: + "Ethereum runs on thousands of servers called nodes. Individual nodes go down, but as long as some nodes are up Ethereum will stay online.", + }, + { + id: "a004-b", + label: "99.99%", + explanation: + "Ethereum runs on thousands of servers called nodes. Individual nodes go down, but as long as some nodes are up Ethereum will stay online.", + }, + { + id: "a004-c", + label: "99.99999%", + explanation: + "Ethereum runs on thousands of servers called nodes. Individual nodes go down, but as long as some nodes are up Ethereum will stay online.", + }, + { + id: "a004-d", + label: "100%", + explanation: + "Ethereum runs on thousands of servers called nodes. Individual nodes go down, but as long as some nodes are up Ethereum will stay online.", + }, + ], + correctAnswerId: "a004-d", + }, + a005: { + prompt: "Ethereum consumes more electricity than:", + answers: [ + { + id: "a005-a", + label: "YouTube", + explanation: + "YouTube uses ~244 Terawatts per year. Ethereum uses 0.01 Terawatts per year.", + }, + { + id: "a005-b", + label: "Netflix", + explanation: + "Netflix uses ~94 Terawatts per year. Ethereum uses 0.01 Terawatts per year.", + }, + { + id: "a005-c", + label: "PayPal", + explanation: + "Paypal uses ~0.26 Terawatts per year. Ethereum uses 0.01 Terawatts per year.", + }, + { + id: "a005-d", + label: "None of the above", + explanation: + "Ethereum uses 0.01 Terawatts per year. Less than YouTube (~244 TW/yr), Netflix (~94 TW/yr), and Paypal (~0.26 TW/yr).", + }, + ], + correctAnswerId: "a005-d", + }, + // What is ether? + b001: { + prompt: "Ether is also known as:", + answers: [ + { + id: "b001-a", + label: "ETC", + explanation: "ETC is the ticker for ether on Ethereum Classic.", + }, + { + id: "b001-b", + label: "ETR", + explanation: + "ETR is not a ticker for ether or any significant cryptocurrency.", + }, + { + id: "b001-c", + label: "ETH", + explanation: "ETH is the ticker for ether on Ethereum.", + }, + { + id: "b001-d", + label: "BTC", + explanation: "BTC is the ticker for bitcoin on the Bitcoin network.", + }, + ], + correctAnswerId: "b001-c", + }, + b002: { + prompt: "On Ethereum, network fees are paid in:", + answers: [ + { + id: "b002-a", + label: "bitcoin", + explanation: + "Lowercase “bitcoin” is the native cryptocurrency of the Bitcoin network.", + }, + { + id: "b002-b", + label: "ETH", + explanation: + "Ether (ETH) is native cryptocurrency of Ethereum. All network fees on Ethereum are paid in ETH.", + }, + { + id: "b002-c", + label: "USD", + explanation: + "It is not possible to pay network fees on Ethereum in USD (US Dollars), or any other FIAT currency.", + }, + { + id: "b002-d", + label: "Ethereum", + explanation: + "Ethereum is the network, but Ethereum’s network fees are paid in the different.", + }, + ], + correctAnswerId: "b002-b", + }, + b003: { + prompt: "Staking on Ethereum helps secure the network because:", + answers: [ + { + id: "b003-a", + label: "Stakers can ban people if they don’t like what they are doing", + explanation: "Stakers are not able to arbitrarily censor users.", + }, + { + id: "b003-b", + label: + "If a staker tries to cheat the network, they risk losing their ETH", + explanation: + "Stakers risk losing a significant amount of their ETH if they are shown to be behaving maliciously against the network. This is known as slashing.", + }, + { + id: "b003-c", + label: "Stakers run powerful computers to demonstrate proof-of-work", + explanation: + "Stakers do not need powerful hardware to stake their ETH. Ethereum stopped using proof-of-work at The Merge.", + }, + { + id: "b003-d", + label: "Stakers undergo KYC before being accepted as a validator", + explanation: + "Staking on Ethereum is permissionless and does not require KYC.", + }, + ], + correctAnswerId: "b003-b", + }, + b004: { + prompt: "ETH is valuable because:", + answers: [ + { + id: "b004-a", + label: "ETH is needed to do anything on Ethereum", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "b004-b", + label: "ETH is an un-censorable peer-to-peer money", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "b004-c", + label: "ETH is used as collateral for crypto loans", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "b004-d", label: "All of the above", explanation: - "Ethereum consumes approximately 0.01 TW/year, while YouTube consumes ~244 TW/year, Netflix ~94 TW/year, and PayPal ~0.26 TW/year.", + "Ethereum transactions cannot be censored, ETH is required to make any transaction on Ethereum, and it is crucial to the stability of the DeFi ecosystem.", + }, + ], + correctAnswerId: "b004-d", + }, + // Web3 + c001: { + prompt: "Web3 allows users to own digital assets directly through:", + answers: [ + { + id: "c001-a", + label: "DAOs", + explanation: + "DAOs (Decentralized autonomous organizations) are member-owned communities without centralized leadership.", + }, + { + id: "c001-b", + label: "NFTs", + explanation: + "NFTs (Non-fungible tokens) provide a way to represent anything unique as an Ethereum-based asset.", + }, + { + id: "c001-c", + label: "ENS", + explanation: + "ENS (Ethereum Name Service) is a decentralized naming service for the Ethereum blockchain.", + }, + { + id: "c001-d", + label: "GitHub", + explanation: + "GitHub is a centralized platform, primarily for storing code using distributed version control. GitHub does not allow ownership of your data or digital assets.", + }, + ], + correctAnswerId: "c001-b", + }, + c002: { + prompt: + "Web1 was read-only, Web2 is read-write, Web3 has been described as:", + answers: [ + { + id: "c002-a", + label: "read-write-sell", + explanation: "Web3 has not been described in this way.", + }, + { + id: "c002-b", + label: "read-write-store", + explanation: "Web3 has not been described in this way.", + }, + { + id: "c002-c", + label: "read-write-own", + explanation: + "Web3 allows users to own their data and has therefore been described as ‘read-write-own’, any improvement on Web2’s, which is only ‘read-write’.", + }, + { + id: "c002-d", + label: "read-write-buy", + explanation: "Web3 has not been described in this way.", + }, + ], + correctAnswerId: "c002-c", + }, + c003: { + prompt: + "Which iteration of the web has native payments built into its infrastructure?", + answers: [ + { + id: "c003-a", + label: "Web1", + explanation: "Web1 didn’t native, built-in payments.", + }, + { + id: "c003-b", + label: "Web2", + explanation: "Web2 does not have native, built-in payments.", + }, + { + id: "c003-c", + label: "Web3", + explanation: + "Web3 has native, built-in payments with cryptocurrencies, such as ETH.", + }, + { + id: "c003-d", + label: "All of the above", + explanation: "Web1 and Web3 do not have native, built-in payments.", + }, + ], + correctAnswerId: "c003-c", + }, + c004: { + prompt: "The term ‘Web3’ was first coined by:", + answers: [ + { + id: "c004-a", + label: "Gavin Wood", + explanation: + "Gavin Wood, a co-founder of Ethereum, is credited with coining the term Web3 shortly after Ethereum launched in 2014.", + }, + { + id: "c004-b", + label: "Steve Jobs", + explanation: "Steve Jobs did not coin the phrase ‘Web3’.", + }, + { + id: "c004-c", + label: "Vitalik Buterin", + explanation: + "Vitalik Buterin, although the original founder of Ethereum, did not coin the phrase ‘Web3’.", + }, + { + id: "c004-d", + label: "Elon Musk", + explanation: "Elon Musk did not coin the phrase ‘Web3’.", + }, + ], + correctAnswerId: "c004-a", + }, + c005: { + prompt: + "You could use a single login across all of the web through the use of:", + answers: [ + { + id: "c005-a", + label: "digital identity", + explanation: + "Digital identity is a high-level concept, not a specific idea that would allow a single login to be used across the web.", + }, + { + id: "c005-b", + label: "centralized identity", + explanation: + "It would not be possible to use a centralized identity provider across all of the web.", + }, + { + id: "c005-c", + label: "proof-of-identity", + explanation: + "Proof-of-identity is a high-level concept, not a specific idea that would allow a single login to be used across the web.", + }, + { + id: "c005-d", + label: "decentralized identity", + explanation: + "Decentralized identity authentication services, such as Sign-in-with-Ethereum, would allow the use of a single login across all of the web.", + }, + ], + correctAnswerId: "c005-d", + }, + // Wallets + d001: { + prompt: "The most secure type of wallet is:", + answers: [ + { + id: "d001-a", + label: "A mobile wallet", + explanation: + "Mobile wallets hold private keys on a mobile device, which typically has connections to the internet, and potentially compromised by other software.", + }, + { + id: "d001-b", + label: "A hardware wallet", + explanation: + "A hardware wallet’s private keys are stored on a dedicated device that can be kept off of the internet and are isolated from other applications on your devices.", + }, + { + id: "d001-c", + label: "A web wallet", + explanation: + "Web wallets have less security than hardware wallets because the private keys are stored on an internet-connected device.", + }, + { + id: "d001-d", + label: "A desktop wallet", + explanation: + "Desktop wallets hold private keys on a computer hard drive, which typically has connections to the internet, and potentially compromised by other software.", + }, + ], + correctAnswerId: "d001-b", + }, + d002: { + prompt: "Which of the following is the best way to store your seed phrase?", + answers: [ + { + id: "d002-a", + label: "In a photo on your phone", + explanation: + "This is not secure. If this photo is uploaded to cloud storage then a hacker gets this image and gains access to your account.", + }, + { + id: "d002-b", + label: "In a file on your computer", + explanation: + "This is not secure. A hacker could get this file and gain access to your account.", + }, + { + id: "d002-c", + label: "Written down on paper", + explanation: + "Of the available options, writing down your seed phrase on paper is the most secure.", + }, + { + id: "d002-d", + label: "In a text message to a trusted family member", + explanation: + "You should never text your seed phrase to anyone. The message could be intercepted by a third party, and even if you trust this person absolutely, you do not know who may be able to access their phone.", + }, + ], + correctAnswerId: "d002-c", + }, + d003: { + prompt: "Who should you give your seed phrase / private keys to:", + answers: [ + { + id: "d003-a", + label: "Someone you’re paying", + explanation: + "You should never give your seed phrase or private keys to anyone. Instead, send tokens to their wallet address via a transaction.", + }, + { + id: "d003-b", + label: "To login to a dapp or wallet", + explanation: + "You should never give your seed phrase / private keys to login to your wallet or dapp.", + }, + { + id: "d003-c", + label: "Support staff", + explanation: + "You should never give your seed phrase / private keys to anyone claiming to be support staff. Anyone asking you for this is a scammer.", + }, + { + id: "d003-d", + label: "No one", + explanation: + "Ideally, you should never give your seed phrase or private keys to anyone. If you trust someone completely with absolute access to your funds (such as a spouse), then you may decide to share this information with them.", + }, + ], + correctAnswerId: "d003-d", + }, + d004: { + prompt: "A wallet and an account on Ethereum are the same thing.", + answers: [ + { + id: "d004-a", + label: "True", + explanation: + "A wallet is a visual interface used to interact with an Ethereum account.", + }, + { + id: "d004-b", + label: "False", + explanation: + "A wallet is a visual interface used to interact with an Ethereum account.", + }, + ], + correctAnswerId: "d004-b", + }, + // Security + e001: { + prompt: "Why should you use unique passwords for all of your accounts?", + answers: [ + { + id: "e001-a", + label: "Incase one of the platforms has a data breach", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "e001-b", + label: + "Incase someone looking over your shoulder works out your password", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "e001-c", + label: "Incase malware, such as a key-logger, steals your password", + explanation: + "This answer is correct, but there are also other correct answers.", + }, + { + id: "e001-d", + label: "All of the above", + explanation: + "All answers are correct. Using unique passwords is the best way to prevent anyone else from accessing your account.", + }, + ], + correctAnswerId: "e001-d", + }, + // Question ID d003 as well + e002: { + prompt: "Following The Merge, ETH must be upgraded to ETH2.", + answers: [ + { + id: "e002-a", + label: "True", + explanation: + "You do not need to upgrade your ETH to ETH2. There is no ETH2 and this is a common narrative used by scammers.", + }, + { + id: "e002-b", + label: "False", + explanation: + "You do not need to upgrade your ETH to ETH2. There is no ETH2 and this is a common narrative used by scammers.", + }, + ], + correctAnswerId: "e002-b", + }, + e003: { + prompt: "ETH giveaways are:", + answers: [ + { + id: "e003-a", + label: "a good way to get more ETH", + explanation: + "ETH giveaways are scams designed to steal your ETH and other tokens. They are never a good way to get more ETH.", + }, + { + id: "e003-b", + label: "always genuine", + explanation: "ETH giveaways are never genuine.", + }, + { + id: "e003-c", + label: "commonly performed by prominent members of the community", + explanation: + "Prominent community members do not do ETH giveaways. Scammers pretend well-know individuals, such as Elon Musk, are doing giveaways to give them the scam a sense of legitimacy.", + }, + { + id: "e003-d", + label: "always a scam", + explanation: + "ETH giveaways are always scams. Reporting and ignoring scammers is best.", + }, + ], + correctAnswerId: "e003-d", + }, + e004: { + prompt: "Ethereum transaction are reversible.", + answers: [ + { + id: "e004-a", + label: "True", + explanation: + "Ethereum transactions cannot be reversed. Anyone who tells you otherwise might be trying to scam you.", + }, + { + id: "e004-b", + label: "False", + explanation: + "Ethereum transactions cannot be reversed. Anyone who tells you otherwise might be trying to scam you.", + }, + ], + correctAnswerId: "e004-b", + }, + // NFTs + f001: { + prompt: "NFTs are most comprehensively defined as:", + answers: [ + { + id: "f001-a", + label: "unique digital assets", + explanation: "NFTs represent a unique digital asset.", // with an owner + }, + { + id: "f001-b", + label: "digital artwork", + explanation: + "NFTs represent a unique digital asset, this is commonly digital artwork, but it isn’t limited to art.", + }, + { + id: "f001-c", + label: "tickets to exclusive events", + explanation: + "NFTs represent a unique digital asset, this is commonly digital artwork, but it isn’t limited to tickets to exclusive events.", + }, + { + id: "f001-d", + label: "legally binding contracts", + explanation: + "Although a legal contract could be represented as an NFT, NFTs are not exclusive to legally binding contracts.", + }, + ], + correctAnswerId: "f001-a", + }, + f002: { + prompt: "Two NFTs representing the same artwork are the same thing.", + answers: [ + { + id: "f002-a", + label: "True", + explanation: + "NFTs are non-fungible, even if they represent the same thing, they are still unique digital assets.", + }, + { + id: "f002-b", + label: "False", + explanation: + "NFTs are non-fungible, even if they represent the same thing, they are still unique digital assets.", + }, + ], + correctAnswerId: "f002-b", + }, + f003: { + prompt: "NFTs most commonly represent:", + answers: [ + { + id: "f003-a", + label: "The password to your wallet", + explanation: "This is a security risk and generally a bad idea!", + }, + { + id: "f003-b", + label: "Ownership of a unique digital item", + explanation: + "NFTs commonly represent ownership of a unique digital item.", + }, + { + id: "f003-c", + label: "Your current ETH balance", + explanation: "NFTs cannot represent your ETH balance arbitrarily.", + }, + { + id: "f003-d", + label: "All of the above", + explanation: + "NFTs commonly represent ownership of a unique digital item, not ETH balances or wallet passwords.", + }, + ], + correctAnswerId: "f003-b", + }, + f004: { + prompt: "NFTs have helped create a new:", + answers: [ + { + id: "f004-a", + label: "curator economy", + explanation: + "NFTs helped create a new economy for creators, not curators.", + }, + { + id: "f004-b", + label: "carbon economy", + explanation: + "NFTs helped create a new economy for creators, not carbon.", + }, + { + id: "f004-c", + label: "creator economy", + explanation: "NFTs helped create the creator economy.", + }, + { + id: "f004-d", + label: "ownership economy", + explanation: + "NFTs helped create a new economy for creators, not ownership.", + }, + ], + correctAnswerId: "f004-c", + }, + f005: { + prompt: "NFTs on Ethereum are harmful to the environment", + answers: [ + { + id: "f005-a", + label: "True", + explanation: + "Since The Merge (transition to proof-of-stake), any transaction has been a negligible impact on the environment.", + }, + { + id: "f005-b", + label: "False", + explanation: + "Since The Merge (transition to proof-of-stake), any transaction has been a negligible impact on the environment.", }, ], - correctAnswerId: "a002-d", + correctAnswerId: "f005-b", }, } From 69b021960e4a83241ed722264bbc05e62037d0af Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:37:45 -0500 Subject: [PATCH 14/56] add QuizWidget to eth and wallets pages --- src/pages-conditional/eth.tsx | 7 +++++++ src/pages-conditional/wallets.tsx | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/pages-conditional/eth.tsx b/src/pages-conditional/eth.tsx index be4de0c0df7..1edb521b9f6 100644 --- a/src/pages-conditional/eth.tsx +++ b/src/pages-conditional/eth.tsx @@ -1,4 +1,5 @@ import React from "react" +import { Center } from "@chakra-ui/react" import styled from "@emotion/styled" import { GatsbyImage } from "gatsby-plugin-image" import { useIntl } from "react-intl" @@ -28,6 +29,7 @@ import { StyledCard, } from "../components/SharedStyledComponents" import FeedbackCard from "../components/FeedbackCard" +import QuizWidget from "../components/Quiz/QuizWidget" import { translateMessageId } from "../utils/translations" import { getImage, getSrc } from "../utils/image" @@ -511,6 +513,11 @@ const EthPage = (props: PageProps) => { ))} + +
+ +
+
diff --git a/src/pages-conditional/wallets.tsx b/src/pages-conditional/wallets.tsx index 266e8906972..fafee332546 100644 --- a/src/pages-conditional/wallets.tsx +++ b/src/pages-conditional/wallets.tsx @@ -1,4 +1,5 @@ import React from "react" +import { Center } from "@chakra-ui/react" import styled from "@emotion/styled" import { GatsbyImage } from "gatsby-plugin-image" import { useIntl } from "react-intl" @@ -22,6 +23,7 @@ import { TwoColumnContent, } from "../components/SharedStyledComponents" import FeedbackCard from "../components/FeedbackCard" +import QuizWidget from "../components/Quiz/QuizWidget" import { translateMessageId } from "../utils/translations" import { getImage, getSrc } from "../utils/image" @@ -451,6 +453,11 @@ const WalletsPage = ({ + +
+ +
+
From 5802edd5233cf99217bf3626679531daa55c95cc Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:56:48 -0500 Subject: [PATCH 15/56] fix quiz svg colors --- src/assets/quiz/correct.svg | 2 +- src/assets/quiz/incorrect.svg | 2 +- src/assets/quiz/trophy.svg | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/quiz/correct.svg b/src/assets/quiz/correct.svg index 7df1f6a3844..abf60946aea 100644 --- a/src/assets/quiz/correct.svg +++ b/src/assets/quiz/correct.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/quiz/incorrect.svg b/src/assets/quiz/incorrect.svg index f391b34f9a6..8dcec009d8c 100644 --- a/src/assets/quiz/incorrect.svg +++ b/src/assets/quiz/incorrect.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/quiz/trophy.svg b/src/assets/quiz/trophy.svg index c3b4f445311..04bb4f16115 100644 --- a/src/assets/quiz/trophy.svg +++ b/src/assets/quiz/trophy.svg @@ -1,4 +1,4 @@ - - + + From 343c1df7d66c2fe7492fbce3ffc0d275af15e139 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 11 Oct 2022 22:44:22 -0500 Subject: [PATCH 16/56] quis adjustments --- src/components/Quiz/QuizWidget.tsx | 6 +++--- src/data/learnQuizzes/questionBank.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 03389634bb0..db80574b377 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -145,7 +145,7 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { return ( quizData && ( - + = ({ quizKey, maxQuestions }) => { )}
- + {showAnswer && currentQuestionAnswerChoice && !currentQuestionAnswerChoice.isCorrect && ( @@ -280,7 +280,7 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { )} {showResults ? ( - + diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts index 08f5c46aedb..0b933ce6b8a 100644 --- a/src/data/learnQuizzes/questionBank.ts +++ b/src/data/learnQuizzes/questionBank.ts @@ -355,7 +355,7 @@ const questionBank: QuestionBank = { { id: "c003-d", label: "All of the above", - explanation: "Web1 and Web3 do not have native, built-in payments.", + explanation: "Web1 and Web2 do not have native, built-in payments.", }, ], correctAnswerId: "c003-c", @@ -534,20 +534,20 @@ const questionBank: QuestionBank = { answers: [ { id: "e001-a", - label: "Incase one of the platforms has a data breach", + label: "In case one of the platforms has a data breach", explanation: "This answer is correct, but there are also other correct answers.", }, { id: "e001-b", label: - "Incase someone looking over your shoulder works out your password", + "In case someone looking over your shoulder works out your password", explanation: "This answer is correct, but there are also other correct answers.", }, { id: "e001-c", - label: "Incase malware, such as a key-logger, steals your password", + label: "In case malware, such as a key-logger, steals your password", explanation: "This answer is correct, but there are also other correct answers.", }, From 219004ffb46ec6046d7b558811a5d05fbbb0903b Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 11 Oct 2022 22:54:03 -0500 Subject: [PATCH 17/56] add QuizWidget to nft/web3/security pages --- src/content/nft/index.md | 2 ++ src/content/security/index.md | 2 ++ src/content/web3/index.md | 2 ++ src/templates/static.tsx | 2 ++ src/templates/use-cases.tsx | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/content/nft/index.md b/src/content/nft/index.md index 5d5fa10c906..37042a73df7 100644 --- a/src/content/nft/index.md +++ b/src/content/nft/index.md @@ -338,3 +338,5 @@ Most NFTs are built using a consistent standard known as [ERC-721](/developers/d - [No, CryptoArtists Aren’t Harming the Planet](https://medium.com/superrare/no-cryptoartists-arent-harming-the-planet-43182f72fc61) - [Ethereum's energy consumption](/energy-consumption/) - [A country's worth of power, no more](https://blog.ethereum.org/2021/05/18/country-power-no-more/) – _Carl Beekhuizen, May 18 2021_ + + diff --git a/src/content/security/index.md b/src/content/security/index.md index 4405dcc8b22..ee5698c839d 100644 --- a/src/content/security/index.md +++ b/src/content/security/index.md @@ -288,3 +288,5 @@ Airdrop scams involve a scam project airdropping an asset (NFT, token) into your - [Staying Safe: Common Scams](https://support.mycrypto.com/staying-safe/common-scams) - _MyCrypto_ - [Avoiding Scams](https://bitcoin.org/en/scams) - _Bitcoin.org_ - [Twitter thread on common crypto phishing emails and messages](https://twitter.com/tayvano_/status/1516225457640787969) - _Taylor Monahan_ + + diff --git a/src/content/web3/index.md b/src/content/web3/index.md index 37115a9a8f3..146dff77674 100644 --- a/src/content/web3/index.md +++ b/src/content/web3/index.md @@ -159,3 +159,5 @@ Web3 isn’t rigidly defined. Various community participants have different pers - [Why Decentralization Matters](https://onezero.medium.com/why-decentralization-matters-5e3f79f7638e) - _Chris Dixon_ - [The Web3 Landscape](https://a16z.com/wp-content/uploads/2021/10/The-web3-Readlng-List.pdf) – _a16z_ - [The Web3 Debate](https://www.notboring.co/p/the-web3-debate?s=r) – _Packy McCormick_ + + diff --git a/src/templates/static.tsx b/src/templates/static.tsx index 1a20a87315d..9f6d4b02974 100644 --- a/src/templates/static.tsx +++ b/src/templates/static.tsx @@ -47,6 +47,7 @@ import YouTube from "../components/YouTube" import TranslationChartImage from "../components/TranslationChartImage" import PostMergeBanner from "../components/Banners/PostMergeBanner" import EnergyConsumptionChart from "../components/EnergyConsumptionChart" +import QuizWidget from "../components/Quiz/QuizWidget" import { getLocaleTimestamp } from "../utils/time" import { isLangRightToLeft, TranslationKey } from "../utils/translations" @@ -154,6 +155,7 @@ const components = { YouTube, TranslationChartImage, EnergyConsumptionChart, + QuizWidget, } const StaticPage = ({ diff --git a/src/templates/use-cases.tsx b/src/templates/use-cases.tsx index 5b59d481435..cf45e4f8cb1 100644 --- a/src/templates/use-cases.tsx +++ b/src/templates/use-cases.tsx @@ -42,6 +42,7 @@ import { import Emoji from "../components/OldEmoji" import YouTube from "../components/YouTube" import FeedbackCard from "../components/FeedbackCard" +import QuizWidget from "../components/Quiz/QuizWidget" import { isLangRightToLeft } from "../utils/translations" import { getSummaryPoints } from "../utils/getSummaryPoints" @@ -171,6 +172,7 @@ const components = { DocLink, ExpandableCard, YouTube, + QuizWidget, } const Title = styled.h1` From 72a786ad8267989343c6ed709b8961181a02c1f8 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 12 Oct 2022 13:34:28 -0500 Subject: [PATCH 18/56] fix genesis year --- src/data/learnQuizzes/questionBank.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/learnQuizzes/questionBank.ts b/src/data/learnQuizzes/questionBank.ts index 0b933ce6b8a..0a9c954e58d 100644 --- a/src/data/learnQuizzes/questionBank.ts +++ b/src/data/learnQuizzes/questionBank.ts @@ -367,7 +367,7 @@ const questionBank: QuestionBank = { id: "c004-a", label: "Gavin Wood", explanation: - "Gavin Wood, a co-founder of Ethereum, is credited with coining the term Web3 shortly after Ethereum launched in 2014.", + "Gavin Wood, a co-founder of Ethereum, is credited with coining the term Web3 shortly after Ethereum launched in 2015.", }, { id: "c004-b", From 1ca0ff5b58cfe893f6e1bbff8add17d759cbcf3d Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:20:01 -0500 Subject: [PATCH 19/56] Update QuizQuestion styling --- src/components/Quiz/QuizQuestion.tsx | 29 +++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx index 252bac9a962..a69827a5ef0 100644 --- a/src/components/Quiz/QuizQuestion.tsx +++ b/src/components/Quiz/QuizQuestion.tsx @@ -22,12 +22,22 @@ const QuizQuestion: React.FC = ({ selectedAnswer, }) => { const { colorMode } = useColorMode() - const { answers, prompt } = questionData + const isDarkMode = colorMode === "dark" + const { answers, prompt, correctAnswerId } = questionData + + // Memoized values const explanation = useMemo(() => { if (!selectedAnswer) return "" return answers.filter(({ id }) => id === selectedAnswer)[0].explanation }, [selectedAnswer]) - + const buttonBg = useMemo(() => { + if (!showAnswer) return "primary" + return correctAnswerId === selectedAnswer ? "green.500" : "red.500" + }, [questionData, selectedAnswer, showAnswer]) + const letterColor = useMemo(() => { + if (!showAnswer) return "white" + return correctAnswerId === selectedAnswer ? "green.500" : "red.500" + }, [questionData, selectedAnswer, showAnswer]) return ( @@ -41,6 +51,7 @@ const QuizQuestion: React.FC = ({ !showAnswer || id === selectedAnswer ? "inline-flex" : "none" return ( + )} + {showResults ? ( + + + + + ) : showAnswer ? ( + + ) : ( )} - {showResults ? ( - - - - - ) : showAnswer ? ( - - ) : ( - - )} - -
-
-
- ) + + + + ) : ( + + + + )} +
+ ) } From d1c384dd766c55ec5e0239c93a561055574b4556 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:35:07 -0500 Subject: [PATCH 22/56] Create QuizRadioGroup.tsx --- src/components/Quiz/QuizRadioGroup.tsx | 164 +++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/components/Quiz/QuizRadioGroup.tsx diff --git a/src/components/Quiz/QuizRadioGroup.tsx b/src/components/Quiz/QuizRadioGroup.tsx new file mode 100644 index 00000000000..85a24dc07d2 --- /dev/null +++ b/src/components/Quiz/QuizRadioGroup.tsx @@ -0,0 +1,164 @@ +// Import libraries +import React, { useMemo } from "react" +import { + Box, + chakra, + Circle, + Flex, + RadioProps, + Text, + useColorMode, + useRadio, + useRadioGroup, +} from "@chakra-ui/react" + +// Import types +import { Question } from "../../types" + +export interface IProps { + questionData: Question + showAnswer: boolean + handleSelection: (answerId: string) => void + selectedAnswer: string | null +} +const QuizRadioGroup: React.FC = ({ + questionData, + showAnswer, + handleSelection, + selectedAnswer, +}) => { + const { prompt, answers, correctAnswerId } = questionData + const { colorMode } = useColorMode() + const isDarkMode = colorMode === "dark" + + // Custom radio button component + interface CustomRadioProps extends RadioProps { + index: number + label: string + } + const CustomRadio: React.FC = ({ + index, + label, + ...radioProps + }) => { + const { state, getInputProps, getCheckboxProps, htmlProps } = + useRadio(radioProps) + const iconBackgroundDark = state.isActive ? "orange.800" : "gray.500" + const iconBackgroundLight = state.isActive ? "blue.300" : "gray.400" + + // Memoized values + const buttonBg = useMemo(() => { + if (!showAnswer) return "primary" + return correctAnswerId === selectedAnswer ? "green.500" : "red.500" + }, [questionData, selectedAnswer, showAnswer]) + const circleBg = useMemo( + () => + showAnswer + ? "white" + : isDarkMode + ? iconBackgroundDark + : iconBackgroundLight, + [showAnswer, isDarkMode] + ) + + // Render CustomRadio + return ( + + + + + + {String.fromCharCode(97 + index).toUpperCase()} + + + {label} + + + ) + } + + const handleChange = (value: string): void => { + handleSelection(value) + } + + const { getRadioProps, getRootProps } = useRadioGroup({ + onChange: handleChange, + }) + + // Memoized values + const explanation = useMemo(() => { + if (!selectedAnswer) return "" + return answers.filter(({ id }) => id === selectedAnswer)[0].explanation + }, [selectedAnswer]) + const letterColor = useMemo(() => { + if (!showAnswer) return "white" + return correctAnswerId === selectedAnswer ? "green.500" : "red.500" + }, [questionData, selectedAnswer, showAnswer]) + + // Render QuizRadioGroup + return ( + + + {prompt} + + + {answers.map(({ id, label }, index) => { + const display = + !showAnswer || id === selectedAnswer ? "inline-flex" : "none" + return ( + + ) + })} + + {showAnswer && ( + + + Explanation + + {explanation} + + )} + + ) +} + +export default QuizRadioGroup From fb9c5bc956e73b879acc5fd5769fd3e1d421ebe1 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:39:51 -0500 Subject: [PATCH 23/56] update QuizWidget with QuizRadioGroup --- src/components/Quiz/QuizWidget.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 95226ac6389..9085d7defd4 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -18,7 +18,7 @@ import { FaTwitter } from "react-icons/fa" // Components import Button from "../Button" -import QuizQuestion from "./QuizQuestion" +import QuizRadioGroup from "./QuizRadioGroup" import QuizSummary from "./QuizSummary" import Translation from "../Translation" @@ -263,7 +263,7 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { questionCount={quizData.questions.length} /> ) : ( - Date: Thu, 20 Oct 2022 18:40:46 -0500 Subject: [PATCH 24/56] update QuizWidget with share tweet functionality small styling adjustments, added void return types for handlers --- src/components/Quiz/QuizWidget.tsx | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 9085d7defd4..3242ad91a22 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -115,37 +115,47 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { ) // Handlers - const handleSelectAnswerChoice = (answerId: string) => { + const handleSelectAnswerChoice = (answerId: string): void => { const isCorrect = answerId === quizData?.questions[currentQuestionIndex].correctAnswerId setCurrentQuestionAnswerChoice({ answerId, isCorrect }) } // TODO: Confirm both handleSelectAnswerChoice & handleSelection are necessary - const handleSelection = (answerId: string) => { + const handleSelection = (answerId: string): void => { setSelectedAnswer(answerId) handleSelectAnswerChoice(answerId) } - const handleShowAnswer = () => { + const handleShowAnswer = (): void => { setShowAnswer(true) } - const handleRetryQuestion = () => { + const handleRetryQuestion = (): void => { setCurrentQuestionAnswerChoice(null) setSelectedAnswer(null) setShowAnswer(false) } - const handleContinue = () => { + const handleContinue = (): void => { if (!currentQuestionAnswerChoice) return setUserQuizProgress((prev) => [...prev, currentQuestionAnswerChoice]) setCurrentQuestionAnswerChoice(null) setShowAnswer(false) } + const shareTweetHandler = (): void => { + if (!quizData || !window) return + const url = `https://ethereum.org${window.location.pathname}` // TODO: Add hash link to quiz + const tweet = `I just took the "${quizData.title}" quiz on ethereum.org and scored ${correctCount} out of ${quizData.questions.length}! Try it yourself at ${url}` + window.open( + `https://twitter.com/intent/tweet?text=${encodeURI( + tweet + )}&hashtags=${"ethereumknowledge"}` + ) + } return ( - + = ({ quizKey, maxQuestions }) => { /> )} -
+
{showAnswer && currentQuestionAnswerChoice && @@ -285,7 +295,10 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { )} {showResults ? ( - From 840d399b944a0ac5367597ec896638a9ce1d74ad Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:41:22 -0500 Subject: [PATCH 25/56] internationalize percentages --- src/components/Quiz/QuizSummary.tsx | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/components/Quiz/QuizSummary.tsx b/src/components/Quiz/QuizSummary.tsx index d82b04ee8e6..f71c8b2b330 100644 --- a/src/components/Quiz/QuizSummary.tsx +++ b/src/components/Quiz/QuizSummary.tsx @@ -1,6 +1,7 @@ // Libraries import React, { useMemo } from "react" import { Box, Flex, Text } from "@chakra-ui/react" +import { useIntl } from "react-intl" export interface IProps { correctCount: number @@ -8,10 +9,17 @@ export interface IProps { } const QuizSummary: React.FC = ({ correctCount, questionCount }) => { + const { locale } = useIntl() const percentCorrect = useMemo( - () => Math.floor((correctCount / questionCount) * 100), + () => correctCount / questionCount, [correctCount, questionCount] ) + const options = { + style: "percent", + maximumFractionDigits: 0, + } + const numberToPercent = (num: number): string => + new Intl.NumberFormat(locale, options).format(num) return ( @@ -34,18 +42,32 @@ const QuizSummary: React.FC = ({ correctCount, questionCount }) => { borderColor="disabled" > - {percentCorrect}% + {numberToPercent(percentCorrect)} Score - + +{correctCount} - Total points + Correct + + + + + {questionCount} + + + Total From 0dd51c26007b121d9c3212b3d0e1f37bbf4a9ac8 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:42:09 -0500 Subject: [PATCH 26/56] deprecate QuizQuestion component --- src/components/Quiz/QuizQuestion.tsx | 104 --------------------------- 1 file changed, 104 deletions(-) delete mode 100644 src/components/Quiz/QuizQuestion.tsx diff --git a/src/components/Quiz/QuizQuestion.tsx b/src/components/Quiz/QuizQuestion.tsx deleted file mode 100644 index a69827a5ef0..00000000000 --- a/src/components/Quiz/QuizQuestion.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// Libraries -import React, { useMemo } from "react" -import { Box, Circle, Text, useColorMode } from "@chakra-ui/react" - -// Components -import Button from "../Button" - -// Types -import { Question } from "../../types" - -export interface IProps { - questionData: Question - showAnswer: boolean - handleSelection: (answerId: string) => void - selectedAnswer: string | null -} - -const QuizQuestion: React.FC = ({ - questionData, - showAnswer, - handleSelection, - selectedAnswer, -}) => { - const { colorMode } = useColorMode() - const isDarkMode = colorMode === "dark" - const { answers, prompt, correctAnswerId } = questionData - - // Memoized values - const explanation = useMemo(() => { - if (!selectedAnswer) return "" - return answers.filter(({ id }) => id === selectedAnswer)[0].explanation - }, [selectedAnswer]) - const buttonBg = useMemo(() => { - if (!showAnswer) return "primary" - return correctAnswerId === selectedAnswer ? "green.500" : "red.500" - }, [questionData, selectedAnswer, showAnswer]) - const letterColor = useMemo(() => { - if (!showAnswer) return "white" - return correctAnswerId === selectedAnswer ? "green.500" : "red.500" - }, [questionData, selectedAnswer, showAnswer]) - return ( - - - {prompt} - - {answers.map(({ id, label }, index) => { - const active = selectedAnswer === id - const iconBackgroundDark = active ? "orange.800" : "gray.500" - const iconBackgroundLight = active ? "blue.300" : "gray.400" - const display = - !showAnswer || id === selectedAnswer ? "inline-flex" : "none" - return ( - - ) - })} - {showAnswer && ( - <> - - Explanation - - {explanation} - - )} - - ) -} - -export default QuizQuestion From 1dfb5149b158b3d0e2b7d4c3232fb77963ba0819 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Sat, 22 Oct 2022 18:29:43 -0500 Subject: [PATCH 27/56] feat: custom matomo tracking for quiz widget --- src/components/Quiz/QuizWidget.tsx | 40 ++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 3242ad91a22..3f7d81ae015 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -32,6 +32,9 @@ import StarConfetti from "../../assets/quiz/star-confetti.svg" import allQuizData from "../../data/learnQuizzes" import questionBank from "../../data/learnQuizzes/questionBank" +// Utilities +import { trackCustomEvent } from "../../utils/matomo" + // Type import { AnswerChoice, RawQuiz, Quiz, RawQuestion, Question } from "../../types" @@ -127,11 +130,24 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { handleSelectAnswerChoice(answerId) } - const handleShowAnswer = (): void => { + const handleShowAnswer = (questionId: string, answer: AnswerChoice): void => { + trackCustomEvent({ + eventCategory: "Quiz widget", + eventAction: "Question answered", + eventName: `QID: ${questionId}`, + eventValue: answer.isCorrect + ? `Correct answer` + : `AID: ${answer.answerId}`, + }) setShowAnswer(true) } const handleRetryQuestion = (): void => { + trackCustomEvent({ + eventCategory: "Quiz widget", + eventAction: "Other", + eventName: "Retry question", + }) setCurrentQuestionAnswerChoice(null) setSelectedAnswer(null) setShowAnswer(false) @@ -141,9 +157,24 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { setUserQuizProgress((prev) => [...prev, currentQuestionAnswerChoice]) setCurrentQuestionAnswerChoice(null) setShowAnswer(false) + if (showResults) { + const ratioCorrect: number = + correctCount / quizData!.questions.length || 0 + trackCustomEvent({ + eventCategory: "Quiz widget", + eventAction: "Other", + eventName: "Submit results", + eventValue: `${Math.floor(ratioCorrect * 100)}%`, + }) + } } const shareTweetHandler = (): void => { if (!quizData || !window) return + trackCustomEvent({ + eventCategory: "Quiz widget", + eventAction: "Other", + eventName: "Share results", + }) const url = `https://ethereum.org${window.location.pathname}` // TODO: Add hash link to quiz const tweet = `I just took the "${quizData.title}" quiz on ethereum.org and scored ${correctCount} out of ${quizData.questions.length}! Try it yourself at ${url}` window.open( @@ -311,7 +342,12 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { ) : ( From fa61dbd66372f2076bf0e209874b63cf8282ba26 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 25 Oct 2022 13:56:40 -0700 Subject: [PATCH 33/56] add explicit quizKey prop --- src/pages-conditional/what-is-ethereum.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages-conditional/what-is-ethereum.tsx b/src/pages-conditional/what-is-ethereum.tsx index 4286f3a4dc7..d25fe576f8d 100644 --- a/src/pages-conditional/what-is-ethereum.tsx +++ b/src/pages-conditional/what-is-ethereum.tsx @@ -947,7 +947,7 @@ const WhatIsEthereumPage = ({
- +
From 22f4b0a5096792690d6f79659008f096717993ea Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:30:48 -0700 Subject: [PATCH 34/56] clean up magic numbers and progress bar logic --- src/components/Quiz/QuizWidget.tsx | 85 +++++++++++++++--------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 8ba2aac73c8..066bf3efd35 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -1,5 +1,5 @@ // Import libraries -import React, { useEffect, useState, useMemo } from "react" +import React, { useEffect, useState, useMemo, useCallback } from "react" import { Box, ButtonGroup, @@ -100,6 +100,33 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { [userQuizProgress, quizData] ) + const progressBarBackground = useCallback( + (index: number): string => { + if ( + (showAnswer && + index === currentQuestionIndex && + currentQuestionAnswerChoice?.isCorrect) || + userQuizProgress[index]?.isCorrect + ) + return "success" + if ( + (showAnswer && + index === currentQuestionIndex && + !currentQuestionAnswerChoice?.isCorrect) || + (userQuizProgress[index] && !userQuizProgress[index].isCorrect) + ) + return "error" + if (index === currentQuestionIndex) return "gray.400" + return "gray.500" + }, + [ + showAnswer, + currentQuestionIndex, + currentQuestionAnswerChoice, + userQuizProgress, + ] + ) + const correctCount = useMemo( () => userQuizProgress.filter(({ isCorrect }) => isCorrect).length, [userQuizProgress] @@ -207,10 +234,8 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { Test your knowledge = ({ quizKey, maxQuestions }) => { } borderRadius="base" boxShadow="drop" - padding={{ - md: "49px 62px", // TODO: Remove magic numbers - base: "20px 30px", - }} + py={{ base: 5, md: 12 }} + px={{ base: 7, md: 15 }} position="relative" > {/* Trophy icon */} @@ -279,40 +302,18 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { {quizData.title}
+ {/* Progress bar */}
- {quizData.questions.map(({ id }, index) => { - let bg: string - if ( - (showAnswer && - index === currentQuestionIndex && - currentQuestionAnswerChoice?.isCorrect) || - userQuizProgress[index]?.isCorrect - ) { - bg = "success" - } else if ( - (showAnswer && - index === currentQuestionIndex && - !currentQuestionAnswerChoice?.isCorrect) || - (userQuizProgress[index] && - !userQuizProgress[index].isCorrect) - ) { - bg = "error" - } else if (index === currentQuestionIndex) { - bg = "gray.400" - } else { - bg = "gray.500" - } - return ( - - ) - })} + {quizData.questions.map(({ id }, index) => ( + + ))}
{showResults ? ( From ce9805ef2d43f8936d99b370522a49cc49fc7493 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:33:14 -0700 Subject: [PATCH 35/56] small layout adjustments --- src/components/Quiz/QuizWidget.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 066bf3efd35..a4e9da6d766 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -227,9 +227,10 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { Test your knowledge @@ -245,7 +246,8 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { } borderRadius="base" boxShadow="drop" - py={{ base: 5, md: 12 }} + pt={12} + pb={{ base: 5, md: 12 }} px={{ base: 7, md: 15 }} position="relative" > From 13a08d30a36f00139f2237569dfd50167c9ecc1a Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:06:00 -0700 Subject: [PATCH 36/56] getLocaleForNumberFormat with numberToPercent --- src/utils/numberToPercent.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/numberToPercent.ts b/src/utils/numberToPercent.ts index d9abac43983..756ded9ebc7 100644 --- a/src/utils/numberToPercent.ts +++ b/src/utils/numberToPercent.ts @@ -1,5 +1,8 @@ -export const numberToPercent = (num: number, locale: string = "en"): string => - new Intl.NumberFormat(locale, { +import { getLocaleForNumberFormat } from "./translations" +import type { Lang } from "./languages" + +export const numberToPercent = (num: number, locale: Lang = "en"): string => + new Intl.NumberFormat(getLocaleForNumberFormat(locale), { style: "percent", maximumFractionDigits: 0, }).format(num) From 7628ecbf2796fa6e9d5c53ed03edae8974d0d059 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:09:44 -0700 Subject: [PATCH 37/56] chore: code clean up --- src/components/Quiz/QuizRadioGroup.tsx | 8 ++++---- src/components/Quiz/QuizSummary.tsx | 2 +- src/components/Quiz/QuizWidget.tsx | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Quiz/QuizRadioGroup.tsx b/src/components/Quiz/QuizRadioGroup.tsx index 836cb4f1761..658da47ab62 100644 --- a/src/components/Quiz/QuizRadioGroup.tsx +++ b/src/components/Quiz/QuizRadioGroup.tsx @@ -86,7 +86,7 @@ const QuizRadioGroup: React.FC = ({ }} > = ({ > = ({ // Render QuizRadioGroup return ( - + {prompt} diff --git a/src/components/Quiz/QuizSummary.tsx b/src/components/Quiz/QuizSummary.tsx index ee51c64fd7f..fa7c074b88d 100644 --- a/src/components/Quiz/QuizSummary.tsx +++ b/src/components/Quiz/QuizSummary.tsx @@ -38,7 +38,7 @@ const QuizSummary: React.FC = ({ correctCount, questionCount }) => { // Render QuizSummary component return ( - + {isPassingScore ? "You passed the quiz!" : "Your results"} = ({ quizKey, maxQuestions }) => { marginInline={0} /> ))} + /> + )
{showResults ? ( From 2cc1e8767a5aca120a27cb8cb462b665808389fb Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:20:23 -0700 Subject: [PATCH 38/56] feat: improve progress bar responsiveness Calculates an ideal width based on the width of the container and the number of questions available, maxing the width at the existing 2rem limit. --- src/components/Quiz/QuizWidget.tsx | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 05b544bb537..7e0eb9e1f3e 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -39,6 +39,9 @@ import { AnswerChoice, RawQuiz, Quiz, RawQuestion, Question } from "../../types" // Import constants import { PASSING_QUIZ_SCORE } from "../../constants" +// Constants +const PROGRESS_BAR_GAP = "4px" + // Interfaces export interface IProps { quizKey?: string @@ -305,19 +308,24 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => {
{/* Progress bar */} -
- {quizData.questions.map(({ id }, index) => ( - - ))} +
+ {quizData.questions.map(({ id }, index) => { + /* Calculate width percent based on number of questions */ + const width = `calc(${Math.floor( + 100 / quizData.questions.length + )}% - ${PROGRESS_BAR_GAP})` + return ( + ) + })}
{showResults ? ( @@ -341,7 +349,7 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { !currentQuestionAnswerChoice.isCorrect && ( From 532b041b525d00831e8460b2175f82c610818dfc Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:55:29 -0700 Subject: [PATCH 39/56] fix: hover outline coloring --- src/components/Quiz/QuizRadioGroup.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Quiz/QuizRadioGroup.tsx b/src/components/Quiz/QuizRadioGroup.tsx index 658da47ab62..ccb84728971 100644 --- a/src/components/Quiz/QuizRadioGroup.tsx +++ b/src/components/Quiz/QuizRadioGroup.tsx @@ -80,8 +80,9 @@ const QuizRadioGroup: React.FC = ({ borderRadius="base" _hover={{ boxShadow: showAnswer ? "none" : "primary", - outlineColor: "primary", - outline: showAnswer ? "none" : "1px solid", + outline: showAnswer + ? "none" + : "1px solid var(--eth-colors-primary)", cursor: showAnswer ? "default" : "pointer", }} > From 7a8e2938b8b8f363aba80b30763af16272e1ed4f Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:30:25 -0700 Subject: [PATCH 40/56] update prop typing for numberToPercent util --- src/utils/numberToPercent.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/numberToPercent.ts b/src/utils/numberToPercent.ts index 756ded9ebc7..c7ec01e8012 100644 --- a/src/utils/numberToPercent.ts +++ b/src/utils/numberToPercent.ts @@ -1,8 +1,11 @@ import { getLocaleForNumberFormat } from "./translations" import type { Lang } from "./languages" -export const numberToPercent = (num: number, locale: Lang = "en"): string => - new Intl.NumberFormat(getLocaleForNumberFormat(locale), { +export const numberToPercent = ( + num: number, + locale: string | Lang = "en" +): string => + new Intl.NumberFormat(getLocaleForNumberFormat(locale as Lang), { style: "percent", maximumFractionDigits: 0, }).format(num) From dc447fdfbbd8d3ae139a203025d7965414c1183d Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:04:37 -0700 Subject: [PATCH 41/56] fix: confetti z-index below copy and trophy --- src/components/Quiz/QuizWidget.tsx | 41 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 7e0eb9e1f3e..9dd3004cb51 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -253,7 +253,29 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { pb={{ base: 5, md: 12 }} px={{ base: 7, md: 15 }} position="relative" + isolation="isolate" > + {showConfetti && ( + <> + + + + )} {/* Trophy icon */} = ({ quizKey, maxQuestions }) => { color="neutral" /> - {showConfetti && ( - <> - - - - )} {quizData ? ( <>
From 4cb155cfd8fcc02089314c76d1c218d2188520e1 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:15:01 -0700 Subject: [PATCH 42/56] fix: button layout stack and stretch buttons on mobile, update "Try again" copy --- src/components/Quiz/QuizWidget.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 9dd3004cb51..285ae500fef 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState, useMemo, useCallback } from "react" import { Box, - ButtonGroup, Center, Circle, Container, @@ -346,7 +345,14 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { )}
- + {showAnswer && currentQuestionAnswerChoice && !currentQuestionAnswerChoice.isCorrect && ( @@ -358,15 +364,15 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { )} {showResults ? ( - + <> - - + + ) : showAnswer ? ( )} - +
) : ( From d08bb9a0d760bc00d182beb0e54a717f17208343 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:17:23 -0700 Subject: [PATCH 43/56] conditionally hide "Try again" on 100% --- src/components/Quiz/QuizWidget.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Quiz/QuizWidget.tsx b/src/components/Quiz/QuizWidget.tsx index 285ae500fef..62a142b849f 100644 --- a/src/components/Quiz/QuizWidget.tsx +++ b/src/components/Quiz/QuizWidget.tsx @@ -371,7 +371,9 @@ const QuizWidget: React.FC = ({ quizKey, maxQuestions }) => { > Share results - + {score < 100 && ( + + )} ) : showAnswer ? (
-
+