diff --git a/apps/dapp/src/assets/images/nexus/BorderFrame.png b/apps/dapp/src/assets/images/nexus/BorderFrame.png new file mode 100644 index 0000000000..bc87cbb501 Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/BorderFrame.png differ diff --git a/apps/dapp/src/assets/images/nexus/Border_Frame.svg b/apps/dapp/src/assets/images/nexus/Border_Frame.svg new file mode 100644 index 0000000000..eabf6a1dce --- /dev/null +++ b/apps/dapp/src/assets/images/nexus/Border_Frame.svg @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dapp/src/assets/images/nexus/KitKat.png b/apps/dapp/src/assets/images/nexus/KitKat.png new file mode 100644 index 0000000000..4489e22917 Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/KitKat.png differ diff --git a/apps/dapp/src/assets/images/nexus/KitKat.svg b/apps/dapp/src/assets/images/nexus/KitKat.svg new file mode 100644 index 0000000000..890e60bd2c --- /dev/null +++ b/apps/dapp/src/assets/images/nexus/KitKat.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dapp/src/assets/images/nexus/Library_Shelf3.png b/apps/dapp/src/assets/images/nexus/Library_Shelf3.png new file mode 100644 index 0000000000..d07db40e93 Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/Library_Shelf3.png differ diff --git a/apps/dapp/src/assets/images/nexus/Library_Shelves5.png b/apps/dapp/src/assets/images/nexus/Library_Shelves5.png new file mode 100644 index 0000000000..734d42565d Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/Library_Shelves5.png differ diff --git a/apps/dapp/src/assets/images/nexus/Library_Shelves_HL.png b/apps/dapp/src/assets/images/nexus/Library_Shelves_HL.png new file mode 100644 index 0000000000..3b53f4610f Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/Library_Shelves_HL.png differ diff --git a/apps/dapp/src/assets/images/nexus/nexusicons_closebook.png b/apps/dapp/src/assets/images/nexus/nexusicons_closebook.png new file mode 100644 index 0000000000..aff4769a9e Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/nexusicons_closebook.png differ diff --git a/apps/dapp/src/assets/images/nexus/temple256.png b/apps/dapp/src/assets/images/nexus/temple256.png new file mode 100644 index 0000000000..7fc2799a82 Binary files /dev/null and b/apps/dapp/src/assets/images/nexus/temple256.png differ diff --git a/apps/dapp/src/components/Button/Button.tsx b/apps/dapp/src/components/Button/Button.tsx index 07b10f8df4..0276d95841 100644 --- a/apps/dapp/src/components/Button/Button.tsx +++ b/apps/dapp/src/components/Button/Button.tsx @@ -5,12 +5,10 @@ import styled, { css } from 'styled-components'; import Loader from '../Loader/Loader'; import useIsMounted from 'hooks/use-is-mounted'; -export interface ButtonProps - extends ButtonStyledProps, - HTMLProps { +export interface ButtonProps extends ButtonStyledProps, HTMLProps { type?: 'submit' | 'reset' | 'button' | undefined; label?: string; - loading?: boolean; + loading?: boolean; onClick?(): Promise | void; } @@ -107,7 +105,7 @@ interface ButtonStyledProps { const ButtonLeadingIcon = styled(Image)` margin: 0 0.3rem 0; -` +`; export const ButtonStyled = styled.button` // common diff --git a/apps/dapp/src/components/Layouts/CoreLayout/index.tsx b/apps/dapp/src/components/Layouts/CoreLayout/index.tsx index 39a4d4a345..72ca256d15 100644 --- a/apps/dapp/src/components/Layouts/CoreLayout/index.tsx +++ b/apps/dapp/src/components/Layouts/CoreLayout/index.tsx @@ -7,8 +7,6 @@ import { theme } from 'styles/theme'; import { useRefreshWalletState } from 'hooks/use-refresh-wallet-state'; import { useWallet } from 'providers/WalletProvider'; -import env from 'constants/env'; - import Header from './Header'; import { NexusSoundscape } from 'components/Pages/Nexus/NexusSoundscape'; @@ -31,7 +29,7 @@ const CoreLayout: FC<{ mode: 'dapp' | 'nexus' }> = (props) => { return ( <>
- {env.featureFlags.nexusOnlyMode && } + {props.mode === 'nexus' && }
diff --git a/apps/dapp/src/components/Pages/Nexus/NexusSoundscape.tsx b/apps/dapp/src/components/Pages/Nexus/NexusSoundscape.tsx index 5050e1ac9c..defddd5679 100644 --- a/apps/dapp/src/components/Pages/Nexus/NexusSoundscape.tsx +++ b/apps/dapp/src/components/Pages/Nexus/NexusSoundscape.tsx @@ -33,8 +33,6 @@ const FullScreenContainer = styled.div` position: absolute; top: 0; left: 0; - height: 100%; - width: 100%; z-index: 9; `; diff --git a/apps/dapp/src/components/Pages/Nexus/Quest/QuestAccordionItem.tsx b/apps/dapp/src/components/Pages/Nexus/Quest/QuestAccordionItem.tsx index 68034b7cf8..5a2e3b874a 100644 --- a/apps/dapp/src/components/Pages/Nexus/Quest/QuestAccordionItem.tsx +++ b/apps/dapp/src/components/Pages/Nexus/Quest/QuestAccordionItem.tsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React from 'react'; import styled from 'styled-components'; interface ListItemProps { @@ -7,6 +7,7 @@ interface ListItemProps { id: string | number; isOpen: boolean; } + const QuestAccordionItem = ({ id, isOpen, SummaryComponent, DetailComponent, ...rest }: ListItemProps) => { return ( div:first-of-type { cursor: pointer; } `; -export default memo(QuestAccordionItem); +export default QuestAccordionItem; diff --git a/apps/dapp/src/components/Pages/Nexus/Quest/QuestCell.tsx b/apps/dapp/src/components/Pages/Nexus/Quest/QuestCell.tsx index 89bd58bf52..5d4b105944 100644 --- a/apps/dapp/src/components/Pages/Nexus/Quest/QuestCell.tsx +++ b/apps/dapp/src/components/Pages/Nexus/Quest/QuestCell.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import Image from '../../../Image/Image'; import { QuestData, RARITY_TYPE } from '../types'; - export interface QuestCellProps { quest: QuestData; } @@ -22,7 +21,9 @@ const QuestCell = ({ quest }: QuestCellProps) => { {`Origin: ${quest.origin}`} - Travel + + Travel + diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/CollectSlide.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/CollectSlide.tsx new file mode 100644 index 0000000000..0bb9602202 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/CollectSlide.tsx @@ -0,0 +1,110 @@ +import styled from 'styled-components'; +import { Button } from 'components/Button/Button'; +import Image from 'components/Image/Image'; +import Slide from './Slide'; +import nexusCloseBook from 'assets/images/nexus/nexusicons_closebook.png'; + +type CollectSlideProps = { + passed: boolean; + tryAgainButtonClickHandler?: () => void; + collectShardButtonClickHandler?: () => void; +}; + +// TODO: Try again should go back to the beginning and reset the entire state +// TODO: Collect shard should handle the actual claiming of the shard + +const CollectSlide = ({ passed, tryAgainButtonClickHandler, collectShardButtonClickHandler }: CollectSlideProps) => { + return ( + + {passed && ( + <> + + + TEMPLAR, YOU ARE DESERVING.
+ AS A SCHOLAR OF THE TEMPLE,
+ YOU ARE REWARDED FOR YOUR PERSEVERANCE IN
+ THE QUEST FOR KNOWLEDGE. + + + +
+
+ + COLLECT SHARD + + + )} + {!passed && ( + <> + + TEMPLAR, YOU ARE ARE NOT READY.
+ FRET NOT, ALL ARE GIVEN ANOTHER CHANCE
+ TO PROVE THEY ARE DESERVING.
+ GO BACK AND TRY AGAIN.
+
+ + TRY AGAIN + + + )} +
+ ); +}; + +const ImageContainer = styled.div` + border: 2px solid; + border-radius: 10px; + display: flex; + padding: 10px; + align-items: center; + margin-top: 10px; + border: 1.5px solid #BD7B4F;; + border-radius: 10px; +`; + +const CollectShardContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const ButtonsContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; +`; + +const TextContainer = styled.div` + border: 0.0625rem solid ${(props) => props.color ?? props.theme.palette.brand}; + border-radius: 16px; + padding: 4rem; + width: 1056px; + text-align: center; + line-height: 40px; + margin-top: 200px; + background: radial-gradient( + 58.56% 58.56% at 50% 49.88%, + rgba(193, 177, 177, 0.12) 38.54%, + rgba(193, 177, 177, 0) 100% + ); + font-size: 20px; +`; + +const StyledButton = styled(Button)` + width: 224px; + height: 61px; + background: linear-gradient(180deg, #504f4f 45.31%, #0c0b0b 100%); + border: 1px solid #bd7b4f; + border-radius: 25px; + color: #ffffff; + margin-top: 80px; + margin-bottom: 20px; + + &:hover { + border: 1px solid; + border-image-source: linear-gradient(0deg, #ffdec9, #ffdec9), linear-gradient(0deg, #d9a37d, #d9a37d); + } +`; + +export default CollectSlide; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FinalSlide.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FinalSlide.tsx new file mode 100644 index 0000000000..4c02c008a3 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FinalSlide.tsx @@ -0,0 +1,65 @@ +import styled from 'styled-components'; +import { Button } from 'components/Button/Button'; +import Slide from './Slide'; + +type FinalSlideProps = { + backButtonClickHandler: () => void; + submitButtonClickHandler: () => void; +}; +const FinalSlide = ({ backButtonClickHandler, submitButtonClickHandler }: FinalSlideProps) => { + return ( + + + YOU HAVE REACHED THE END OF THE QUIZ.
+ CLICK SUBMIT TO FINALISE YOUR SCORE. +
+ GO BACK IF YOU ARE NOT READY TO ASCEND.
+
+ + BACK + SUBMIT + +
+ ); +}; + +const ButtonsContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; +`; + +const TextContainer = styled.div` + border: 0.0625rem solid ${(props) => props.color ?? props.theme.palette.brand}; + border-radius: 16px; + padding: 4rem; + width: 1056px; + text-align: center; + line-height: 40px; + margin-top: 200px; + background: radial-gradient( + 58.56% 58.56% at 50% 49.88%, + rgba(193, 177, 177, 0.12) 38.54%, + rgba(193, 177, 177, 0) 100% + ); + font-size: 20px; +`; + +const StyledButton = styled(Button)` + width: 174px; + height: 61px; + background: linear-gradient(180deg, #504f4f 45.31%, #0c0b0b 100%); + border: 1px solid #bd7b4f; + border-radius: 25px; + color: #ffffff; + margin-right: 20px; + margin-top: 80px; + margin-bottom: 20px; + + &:hover { + border: 1px solid; + border-image-source: linear-gradient(0deg, #ffdec9, #ffdec9), linear-gradient(0deg, #d9a37d, #d9a37d); + } +`; + +export default FinalSlide; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FirstSlide.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FirstSlide.tsx new file mode 100644 index 0000000000..3c80c8df76 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/FirstSlide.tsx @@ -0,0 +1,52 @@ +import styled from 'styled-components'; +import { Button } from 'components/Button/Button'; +import Slide from './Slide'; + +type FirstSlideProps = { + startButtonClickHandler: () => void; +}; + +const FirstSlide = ({ startButtonClickHandler }: FirstSlideProps) => { + return ( + + + Templar, you’ve made it this far.
+ Your journey has only just begun through the hallways of Temple.
+ The Library is but the first step in a lifetime of learning.
+ But, first, answer these riddles so we know you truly are one of us.
+ The Temple rewards its Scholars. +
+ Start +
+ ); +}; + +const TextContainer = styled.div` + border: 0.0625rem solid ${(props) => props.color ?? props.theme.palette.brand}; + border-radius: 16px; + padding: 4rem; + width: 1056px; + text-align: center; + line-height: 40px; + margin-top: 200px; + background: radial-gradient(58.56% 58.56% at 50% 49.88%, rgba(193, 177, 177, 0.12) 38.54%, rgba(193, 177, 177, 0) 100%); + font-size: 20px; +`; + +const SlideStartButton = styled(Button)` + width: 174px; + height: 61px; + background: linear-gradient(180deg, #504f4f 45.31%, #0c0b0b 100%); + border: 1px solid #BD7B4F; + border-radius: 25px; + color: #ffffff; + margin-top: 80px; + margin-bottom: 20px; + + &:hover { + border: 1px solid; + border-image-source: linear-gradient(0deg, #FFDEC9, #FFDEC9), linear-gradient(0deg, #D9A37D, #D9A37D); + } +`; + +export default FirstSlide; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/ProgressBar.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/ProgressBar.tsx new file mode 100644 index 0000000000..4b8599512b --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/ProgressBar.tsx @@ -0,0 +1,58 @@ +import styled from 'styled-components'; + +type QuestionSlideProps = { + questionNumber: number; + totalQuestions: number; +}; + +const ProgressBar = ({ questionNumber, totalQuestions }: QuestionSlideProps) => { + const percentProgress = questionNumber / totalQuestions; + console.log(percentProgress); + return ( + + + + + + {questionNumber}/{totalQuestions} + + + ); +}; + +const ParentContainer = styled.div` + display: flex; + flex-direction: column; + width: 1000px; + position: relative; +`; + +const QuestionStatus = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin-top: 25px; + font-size: 24px; + line-height: 30px; +`; + +const ProgressContainer = styled.div` + box-sizing: border-box; + width: 1000px; + height: 10px; + border: 1px solid #bd7b4f; + border-radius: 20px; +`; + +interface ProgressIndicatorProps { + percentProgress: number; +} + +const ProgressIndicator = styled.div` + width: ${props => (props.percentProgress * 1000)}px; + height: 10px; + background: #bd7b4f; + border-radius: 20px; +`; + +export default ProgressBar; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/QuestionSlide.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/QuestionSlide.tsx new file mode 100644 index 0000000000..1787e53794 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/QuestionSlide.tsx @@ -0,0 +1,145 @@ +import { Button } from 'components/Button/Button'; +import { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import ProgressBar from './ProgressBar'; +import { QuizAnswer, QuizQuestion } from './questions'; +import Slide from './Slide'; + +type QuestionSlideProps = { + prevButtonClickHandler: () => void; + nextButtonClickHandler: () => void; + answerHandler: (questionNumber: number, answer: QuizAnswer, selectedAnswerIndex: number) => void; + question: QuizQuestion; + questionNumber: number; + totalQuestions: number; + initialSelectedAnswer?: number; +}; + +const QuestionSlide = ({ + questionNumber, + totalQuestions, + prevButtonClickHandler, + nextButtonClickHandler, + answerHandler, + question, + initialSelectedAnswer, +}: QuestionSlideProps) => { + const handleAnswer = (selectedAnswer: QuizAnswer, selectedAnswerIndex: number) => { + setAnswerSelected(true); + setAnswerNumber(selectedAnswerIndex + 1); + answerHandler(questionNumber, selectedAnswer, selectedAnswerIndex + 1); + }; + + console.log('here'); + console.log(initialSelectedAnswer); + + const [answerSelected, setAnswerSelected] = useState(false); + const [answerNumber, setAnswerNumber] = useState(0); + + useEffect(() => { + setAnswerSelected(false); + }, [question]); + + useEffect(() => { + if (initialSelectedAnswer) { + setAnswerNumber(initialSelectedAnswer); + setAnswerSelected(true); + } + }, []); + + return ( + + + {question.question} + + {question.answers.map((a, i) => ( + handleAnswer(a, i)} selected={answerNumber === i + 1}> + {a.answer} + + ))} + + + + Previous + + Next + + + + + ); +}; + +const ControlButton = styled.button` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 6px 22px; + background: linear-gradient(180deg, #504f4f 45.31%, #0c0b0b 100%); + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + width: 92px; + height: 27px; + font-size: 12px; + color: #ffffff; + margin: 10px; + border: 0.0625rem solid ${(props) => props.color ?? props.theme.palette.brand}; + border-radius: 12px; + + &:disabled { + color: grey; + border: 0; + } +`; + +// TODO: props for highlighted answer or not +type AnswerButtonProps = { + selected: boolean; +}; + +const AnswerButton = styled.button` + text-align: left; + cursor: pointer; + width: 600px; + height: 42px; + background: linear-gradient(180deg, #504f4f 45.31%, #0c0b0b 100%); + border: 1px solid #bd7b4f; + border-radius: 5px; + color: #ffffff; + margin: 15px; + font-size: 24px; + text-decoration: ${(props) => (props.selected ? 'underline' : 'none')}; + + &:hover { + border: 1px solid; + border-image-source: linear-gradient(0deg, #ffdec9, #ffdec9), linear-gradient(0deg, #d9a37d, #d9a37d); + } +`; + +const AnswerButtons = styled.div` + display: flex; + flex-direction: column; + margin-top: 70px; + margin-bottom: 70px; +`; + +const QuestionText = styled.div` + text-transform: uppercase; + font-size: 22px; + width: 500px; +`; + +const ControlsContainer = styled.div` + display: flex; + flex-direction: row; + margin-top: 20px; +`; + +const QuestionContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin-top: 100px; +`; + +export default QuestionSlide; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/Slide.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/Slide.tsx new file mode 100644 index 0000000000..610b52a6d5 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/Slide.tsx @@ -0,0 +1,70 @@ +import styled from 'styled-components'; +import Image from 'components/Image/Image'; +import borderFrame from 'assets/images/nexus/Border_Frame.svg'; +import headerImage from 'assets/images/nexus/KitKat.png'; +import temple256 from 'assets/images/nexus/temple256.png'; +import { backgroundImage } from 'styles/mixins'; + +type SlideProps = { + headerText: string; +}; + +const Slide: React.FC = ({ headerText, children }) => { + return ( + + + + + + {headerText} + + + {children} + + ); +}; + +const SlideSeparator = styled.hr` + width: 1266px; + border: 1px solid #bd7b4f; +`; + +const SlideHeaderLogoContainer = styled.div` + align-items: left; + width: 100%; + padding-top: 10px; + padding-left: 10px; +`; + +const SlideHeaderTextContainer = styled.div` + display: flex; + justify-content: flex-end; + align-items: flex-end; + width: 100%; + padding: 10px; + margin-right: 50px; + font-size: 24px; +`; + +const SlideContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + +const SlideHeader = styled.div` + display: flex; + ${backgroundImage(headerImage, { size: 'cover' })} + width: 1400px; +`; + +const SlideContent = styled.div` + display: flex; + flex-direction: column; + align-items: center; + width: 1400px; + ${backgroundImage(borderFrame, { size: 'cover' })} + height: 876px; +`; + +export default Slide; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/index.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/index.tsx new file mode 100644 index 0000000000..d5e155f3b7 --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/index.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState } from 'react'; +import CollectSlide from './CollectSlide'; +import FinalSlide from './FinalSlide'; +import FirstSlide from './FirstSlide'; +import { QuizQuestion, pickQuestions, QUIZ_QUESTIONS, QuizAnswer } from './questions'; +import QuestionSlide from './QuestionSlide'; + +const NUMBER_OF_QUESTIONS = 10; + +const PASSING_SCORE = 0.7; + +type QuizAnswerState = { + [key: string]: { + answer: QuizAnswer; + selectedAnswerIndex: number; + }; +}; + +const EMPTY_QUIZ_ANSWER_STATE: QuizAnswerState = {}; + +const Quiz = () => { + const [questionNumber, setQuestionNumber] = useState(0); + const [questions, setQuestions] = useState([] as QuizQuestion[]); + const [passed, setPassed] = useState(false); + const [complete, setComplete] = useState(false); + const [score, setScore] = useState(0); + + const [answers, setAnswers] = useState(EMPTY_QUIZ_ANSWER_STATE); + + useEffect(() => { + setQuestions(pickQuestions(NUMBER_OF_QUESTIONS)); + }, []); + + const startQuizHandler = () => { + setQuestionNumber(1); + }; + + const prevButtonHandler = () => { + setQuestionNumber(questionNumber - 1); + }; + + const nextButtonHandler = () => { + setQuestionNumber(questionNumber + 1); + }; + + const submitButtonHandler = () => { + console.debug(`---> Submit button clicked, calculating score`); + const correctAnswers = Object.values(answers).filter((answer) => answer.answer.correct === true); + setScore(correctAnswers.length / NUMBER_OF_QUESTIONS); + }; + + const quizAnswerHandler = (questionNumber: number, answer: QuizAnswer, selectedAnswerIndex: number) => { + setAnswers({ + ...answers, + [questionNumber]: { + answer, + selectedAnswerIndex, + }, + }); + }; + + const tryAgainButtonClickHandler = () => { + // Reset state to beginning of quiz + setComplete(false); + setQuestions(pickQuestions(NUMBER_OF_QUESTIONS)); + setQuestionNumber(0); + setAnswers(EMPTY_QUIZ_ANSWER_STATE); + }; + + useEffect(() => { + console.debug(`---> User score: ${score}`); + if (score >= PASSING_SCORE) { + setPassed(true); + setComplete(true); + } else { + setPassed(false); + setComplete(true); + } + }, [score]); + + useEffect(() => { + setComplete(false); + }, []); + + return ( + <> + {questionNumber === 0 && } + {questionNumber > 0 && questionNumber <= NUMBER_OF_QUESTIONS && ( + + )} + {!complete && questionNumber > NUMBER_OF_QUESTIONS && ( + + )} + {complete && } + + ); +}; + +export default Quiz; diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/questions.ts b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/questions.ts new file mode 100644 index 0000000000..ea7cef748d --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/Quiz/questions.ts @@ -0,0 +1,442 @@ +// TODO: get these from env file + +export type QuizQuestion = { + question: string; + answers: QuizAnswer[]; +}; + +export type QuizAnswer = { + answer: string; + correct: boolean; +}; + +export const QUIZ_QUESTIONS = [ + { + question: 'What is TPI?', + answers: [ + { + answer: 'A. Treasury Price Incoom', + correct: false, + }, + { + answer: 'B. Temple Protection Implementation', + correct: false, + }, + { + answer: 'C. Templar Pleasure Index', + correct: false, + }, + { + answer: 'D. Treasury Price Index', + correct: true, + }, + ], + }, + { + question: 'What Defi mechanic does RAMOS utilize?', + answers: [ + { + answer: 'A. Automated Market Maker', + correct: false, + }, + { + answer: 'B. Annual Marriage Offerings', + correct: false, + }, + { + answer: 'C. Algorithmic Market Operations', + correct: true, + }, + { + answer: 'D. Accredited Management Organization', + correct: false, + }, + ], + }, + { + question: 'What are Ritual Chambers?', + answers: [ + { + answer: 'A. Temple Projects', + correct: true, + }, + { + answer: 'B. Metaverse Locations', + correct: false, + }, + { + answer: 'C. Love Love Hotels', + correct: false, + }, + { + answer: 'D. Discord Channels', + correct: false, + }, + ], + }, + { + question: + 'What is the likelihood of a rebalance event if temple spot price is at least -1% or +3% away from Treasury Price Index?', + answers: [ + { + answer: 'A. 1%', + correct: false, + }, + { + answer: 'B. 5%', + correct: true, + }, + { + answer: 'C. 50%', + correct: false, + }, + { + answer: 'D. 100%', + correct: false, + }, + ], + }, + { + question: + 'What is the likelihood of a Rebalance event if TEMPLE spot price is more than -3% lower than the Treasury Price Index?', + answers: [ + { + answer: 'A. 1%', + correct: false, + }, + { + answer: 'B. 5%', + correct: false, + }, + { + answer: 'C. 50%', + correct: false, + }, + { + answer: 'D. 100%', + correct: true, + }, + ], + }, + { + question: 'Which Enclave makes deliberations on the Treasury Strategic Proposals?', + answers: [ + { + answer: 'A. Mystery', + correct: false, + }, + { + answer: 'B. Structure', + correct: false, + }, + { + answer: 'C. Logic', + correct: true, + }, + { + answer: 'D. Order', + correct: false, + }, + ], + }, + { + question: 'Relics which act as passport NFTs for Templars conform to which ERC standard?', + answers: [ + { + answer: 'A. ERC-4262', + correct: false, + }, + { + answer: 'B. ERC-1155', + correct: false, + }, + { + answer: 'C. ERC-721', + correct: true, + }, + { + answer: 'D. ERC-6969', + correct: false, + }, + ], + }, + { + question: 'The reward a user receives for completing Nexus Quests are called', + answers: [ + { + answer: 'A. Waifus', + correct: false, + }, + { + answer: 'B. Liquid Tokens', + correct: false, + }, + { + answer: 'C. Shards', + correct: true, + }, + { + answer: 'D. Honey', + correct: false, + }, + ], + }, + { + question: 'How does RAMOS rebalance to raise TEMPLE spot price?', + answers: [ + { + answer: 'A. Pulling TEMPLE single-sidedly from LP', + correct: true, + }, + { + answer: 'B. Burning incense', + correct: false, + }, + { + answer: 'C. Staking TEMPLE into Core Vault', + correct: false, + }, + { + answer: 'D. Add USD single-sidedly into LP', + correct: false, + }, + ], + }, + { + question: 'How does RAMOS mitigate arbitrage, front-running, and MEV attacks?', + answers: [ + { + answer: 'A. Limit Orders', + correct: false, + }, + { + answer: 'B. Randomisation', + correct: true, + }, + { + answer: 'C. Insurance', + correct: false, + }, + { + answer: 'D. Use Flashbots', + correct: false, + }, + ], + }, + { + question: 'Who is responsible for managing the work within a Rituals Chamber?', + answers: [ + { + answer: 'A. Founder', + correct: false, + }, + { + answer: 'B. President', + correct: false, + }, + { + answer: 'C. Chairman', + correct: false, + }, + { + answer: 'D. MC', + correct: true, + }, + ], + }, + { + question: 'Which is the primary LP managed by RAMOS?', + answers: [ + { + answer: 'A. TEMPLE/bb-e-USD', + correct: true, + }, + { + answer: 'B. TEMPLE/FRAX', + correct: false, + }, + { + answer: 'C. frxETH/ETH', + correct: false, + }, + { + answer: 'D. TEMPLE/gOHM', + correct: false, + }, + ], + }, + { + question: 'What can you possibly gain from earning and minting a Shard?', + answers: [ + { + answer: 'A. Whitelisted access to future launches or drops', + correct: false, + }, + { + answer: 'B. Mint pass for NFT projects', + correct: false, + }, + { + answer: 'C. Tradeable NFT rewards from completed quests', + correct: false, + }, + { + answer: 'D. All of the above', + correct: true, + }, + ], + }, + { + question: 'What is the accurate lexicon for Origami Tokens?', + answers: [ + { + answer: 'A. tToken/tvToken', + correct: false, + }, + { + answer: 'B. oToken/ovToken', + correct: true, + }, + { + answer: 'C. jpgToken/jpegToken', + correct: false, + }, + { + answer: 'D. oToken/oreoToken', + correct: false, + }, + ], + }, + { + question: 'The reserve token to vault share token ratio in an Origami vault is equal to', + answers: [ + { + answer: 'A. reservePerShare', + correct: true, + }, + { + answer: 'B. reservePerTemple', + correct: false, + }, + { + answer: 'C. reservePerOrigami', + correct: false, + }, + { + answer: 'D. TPI/TEMPLE', + correct: false, + }, + ], + }, + { + question: 'The TempleDAO Opening Ceremony took place in the', + answers: [ + { + answer: 'A. ETHDenver', + correct: false, + }, + { + answer: 'B. Nexus', + correct: false, + }, + { + answer: 'C. Metaverse', + correct: true, + }, + { + answer: 'D. Ibiza', + correct: false, + }, + ], + }, + { + question: 'Origami Vault Reserve Tokens (oTokens) are backed by the underlying token at a ratio of', + answers: [ + { + answer: 'A. 1:1', + correct: true, + }, + { + answer: 'B. 1:2', + correct: false, + }, + { + answer: 'C. 1:10', + correct: false, + }, + { + answer: 'D. 2:1', + correct: false, + }, + ], + }, + { + question: 'Nexus Quest Shards should be stored in a', + answers: [ + { + answer: 'A. Metamask wallet', + correct: false, + }, + { + answer: 'B. Relic', + correct: true, + }, + { + answer: 'C. Core Vault', + correct: false, + }, + { + answer: 'D. Treasure Chest', + correct: false, + }, + ], + }, + { + question: 'The process of using the Forge to convert one Shard to another is called', + answers: [ + { + answer: 'A. Metamorphosis', + correct: false, + }, + { + answer: 'B. Transformation', + correct: false, + }, + { + answer: 'C. Transmutation', + correct: true, + }, + { + answer: 'D. Transfiguration', + correct: false, + }, + ], + }, + { + question: + 'Origami is a _____ protocol that supercharges yield for any supported DeFi protocol to maximize returns and provide on-demand liquidity.', + answers: [ + { + answer: 'A. Vote escrow staking', + correct: false, + }, + { + answer: 'B. Liquid wrapper', + correct: true, + }, + { + answer: 'C. Proof of liquidity', + correct: false, + }, + { + answer: 'D. Liquidation engine', + correct: false, + }, + ], + }, +]; + +export const pickQuestions = (numberOfQuestions: number) => { + const shuffled = [...QUIZ_QUESTIONS].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, numberOfQuestions); +} \ No newline at end of file diff --git a/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/library.tsx b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/library.tsx new file mode 100644 index 0000000000..3ef4a4b22f --- /dev/null +++ b/apps/dapp/src/components/Pages/Nexus/Quests/FirstQuest/library.tsx @@ -0,0 +1,23 @@ +import styled from 'styled-components'; +import Image from 'components/Image/Image'; + +import libraryShelfImage from 'assets/images/nexus/Library_Shelf3.png'; + +const NexusLibrary = () => { + return ( + + + + ); +}; + +const LibraryContainer = styled.div` + display: flex; +`; + +const BooksImage = styled(Image)` + width: 100%; + height: 100%; +`; + +export default NexusLibrary; diff --git a/apps/dapp/src/constants/env/preview-nexus.tsx b/apps/dapp/src/constants/env/preview-nexus.tsx index 815c25d8d7..f2ba8bceb6 100644 --- a/apps/dapp/src/constants/env/preview-nexus.tsx +++ b/apps/dapp/src/constants/env/preview-nexus.tsx @@ -150,21 +150,21 @@ const env: Environment = { }, }, quests: [ - // { - // id: '1', - // title: 'Spirit of the Sands', - // origin: 'TempleDAO', - // linkUrl: 'https://templedao.link', - // description: - // 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi accumsan libero sed placerat viverra. Praesent ac vehicula mauris, non ullamcorper metus. Vestibulum ultricies odio at libero pulvinar dapibus sed vel leo.', - // logoUrl: 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // rewardLogoUrls: [ - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // ], - // rarity: RARITY_TYPE.EPIC, - // }, + { + id: '1', + title: 'Spirit of the Sands', + origin: 'TempleDAO', + linkUrl: 'https://templedao.link', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi accumsan libero sed placerat viverra. Praesent ac vehicula mauris, non ullamcorper metus. Vestibulum ultricies odio at libero pulvinar dapibus sed vel leo.', + logoUrl: 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + rewardLogoUrls: [ + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + ], + rarity: RARITY_TYPE.EPIC, + }, ], }, }; diff --git a/apps/dapp/src/constants/env/preview.tsx b/apps/dapp/src/constants/env/preview.tsx index 081c13a5b4..f2ba8bceb6 100644 --- a/apps/dapp/src/constants/env/preview.tsx +++ b/apps/dapp/src/constants/env/preview.tsx @@ -38,7 +38,6 @@ const env: Environment = { templeCore: 'https://api.thegraph.com/subgraphs/name/templedao/templedao-core-goerli', protocolMetrics: 'https://api.thegraph.com/subgraphs/name/templedao/templedao-metrics', balancerV2: 'https://api.thegraph.com/subgraphs/name/templedao/templedao-balancer-v2', - ramos: 'https://api.thegraph.com/subgraphs/name/templedao/templedao-ramos', // Original Balancer Subgraph // balancerV2: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-goerli-v2', }, @@ -108,7 +107,7 @@ const env: Environment = { }, }, network: 5, - etherscan: 'https://goerli.etherscan.io', + etherscan: 'https://goerli.arbiscan.io', featureFlags: { enableAscend: false, nexusOnlyMode: true, @@ -151,21 +150,21 @@ const env: Environment = { }, }, quests: [ - // { - // id: '1', - // title: 'Spirit of the Sands', - // origin: 'TempleDAO', - // linkUrl: 'https://templedao.link', - // description: - // 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi accumsan libero sed placerat viverra. Praesent ac vehicula mauris, non ullamcorper metus. Vestibulum ultricies odio at libero pulvinar dapibus sed vel leo.', - // logoUrl: 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // rewardLogoUrls: [ - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', - // ], - // rarity: RARITY_TYPE.EPIC, - // }, + { + id: '1', + title: 'Spirit of the Sands', + origin: 'TempleDAO', + linkUrl: 'https://templedao.link', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi accumsan libero sed placerat viverra. Praesent ac vehicula mauris, non ullamcorper metus. Vestibulum ultricies odio at libero pulvinar dapibus sed vel leo.', + logoUrl: 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + rewardLogoUrls: [ + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + 'https://myst.mypinata.cloud/ipfs/QmaTErwf7sV9WzfP86GjDfnRBwKL74y2j9H4vUwNr7jMhE/0.png', + ], + rarity: RARITY_TYPE.EPIC, + }, ], }, }; diff --git a/apps/dapp/src/main.tsx b/apps/dapp/src/main.tsx index ed7341824d..32cb7d094a 100644 --- a/apps/dapp/src/main.tsx +++ b/apps/dapp/src/main.tsx @@ -35,6 +35,8 @@ import ForgePage from 'components/Pages/Nexus/Forge'; import NexusUserManual from 'components/Pages/Nexus/Manual/UserManual'; import NexusPartnerManual from 'components/Pages/Nexus/Manual/PartnerManual'; import { NexusGates } from 'components/Pages/Nexus/NexusGates'; +import NexusLibrary from 'components/Pages/Nexus/Quests/FirstQuest/library'; +import Quiz from 'components/Pages/Nexus/Quests/FirstQuest/Quiz'; // Separate Chunks const TeamPayments = React.lazy(() => import('components/Pages/TeamPayments')); @@ -123,6 +125,8 @@ ReactDOM.render( } /> } /> + } /> + } /> )}