update,
This commit is contained in:
63
03_source/mobile/src/pages/DemoQuizApp/AppPages/Home.jsx
Normal file
63
03_source/mobile/src/pages/DemoQuizApp/AppPages/Home.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonActionSheet,
|
||||
} from '@ionic/react';
|
||||
import styles from './Home.module.scss';
|
||||
|
||||
import { informationCircleOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const [show, hide] = useIonActionSheet();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
<IonCol size="12" className="ion-text-center">
|
||||
<img src="/assets/DemoQuizApp/main.png" alt="title" className={styles.title} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
<IonRow className={styles.buttons}>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
routerLink="/demo-quiz-app/quiz"
|
||||
color="light"
|
||||
expand="block"
|
||||
className={styles.playButton}
|
||||
>
|
||||
Start Playing
|
||||
</IonButton>
|
||||
|
||||
<IonButton
|
||||
color="dark"
|
||||
className={styles.helpButton}
|
||||
onClick={() =>
|
||||
show({
|
||||
buttons: [{ text: 'Close' }],
|
||||
header: 'How to play',
|
||||
subHeader:
|
||||
'Pick a category and difficulty, then proceed to answer each question. You will gain a score by getting an answer right and you will also be indicated whether your answer was correct or incorrect. Have fun!',
|
||||
})
|
||||
}
|
||||
>
|
||||
<IonIcon icon={informationCircleOutline} /> How to play
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,43 @@
|
||||
.title {
|
||||
|
||||
height: 10rem;
|
||||
margin-top: 30%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
||||
position: absolute;
|
||||
bottom: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
|
||||
height: 4rem;
|
||||
--border-radius: 500px;
|
||||
width: fit-content;
|
||||
--padding-start: 5rem;
|
||||
--padding-end: 5rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.helpButton {
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
margin-top: 3rem;
|
||||
opacity: 70%;
|
||||
--border-radius: 10rem !important;
|
||||
--padding-end: 1.25rem;
|
||||
|
||||
ion-icon {
|
||||
|
||||
margin-top: 0.2rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
193
03_source/mobile/src/pages/DemoQuizApp/AppPages/Questions.jsx
Normal file
193
03_source/mobile/src/pages/DemoQuizApp/AppPages/Questions.jsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
useIonViewDidEnter,
|
||||
} from '@ionic/react';
|
||||
|
||||
import styles from './Quiz.module.scss';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { SettingsStore } from '../store';
|
||||
import {
|
||||
getCategories,
|
||||
getChosenCategory,
|
||||
getChosenDifficulty,
|
||||
getDifficulties,
|
||||
} from '../store/Selectors';
|
||||
import { Category, Difficulty } from '../components/Settings';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { fetchQuestions } from '../questions';
|
||||
|
||||
// Import Swiper React components
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
|
||||
// Import Swiper styles
|
||||
// import 'swiper/swiper.scss';
|
||||
import 'swiper/css';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
import { Answer } from '../components/Answer';
|
||||
import { CompletedCard } from '../components/CompletedCard';
|
||||
import { QuizStats } from '../components/QuizStats';
|
||||
|
||||
const Questions = () => {
|
||||
const mainContainerRef = useRef();
|
||||
const completionContainerRef = useRef();
|
||||
const swiperRef = useRef(null);
|
||||
|
||||
const router = useIonRouter();
|
||||
const chosenCategory = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
|
||||
const [currentQuestion, setCurrentQuestion] = useState(1);
|
||||
const [score, setScore] = useState(0);
|
||||
const [completed, setCompleted] = useState(false);
|
||||
|
||||
const [questions, setQuestions] = useState(false);
|
||||
const [slideSpace, setSlideSpace] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getQuestions = async () => {
|
||||
const fetchedQuestions = await fetchQuestions(chosenCategory, chosenDifficulty);
|
||||
setQuestions(fetchedQuestions);
|
||||
};
|
||||
|
||||
getQuestions();
|
||||
}, []);
|
||||
|
||||
useIonViewDidEnter(() => {
|
||||
setSlideSpace(40);
|
||||
});
|
||||
|
||||
const handleAnswerClick = (event, answer, question) => {
|
||||
const isCorrect = question.correct_answers[`${answer}_correct`] === 'true';
|
||||
|
||||
if (isCorrect) {
|
||||
event.target.setAttribute('color', 'success');
|
||||
} else {
|
||||
event.target.setAttribute('color', 'danger');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
isCorrect && setScore((score) => score + 1);
|
||||
event.target.setAttribute('color', 'light');
|
||||
swiperRef.current.swiper.slideNext();
|
||||
checkIfComplete();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const checkIfComplete = () => {
|
||||
if (currentQuestion === questions.length) {
|
||||
// Quiz has finished
|
||||
// Hide Slides and show completion screen
|
||||
mainContainerRef.current.classList.add('animate__zoomOutDown');
|
||||
|
||||
setTimeout(() => {
|
||||
setCompleted(true);
|
||||
completionContainerRef.current.classList.add('animate__zoomInUp');
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
<img src="/assets/DemoQuizApp/main.png" style={{ width: '30%' }} alt="logo" />
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen className="background">
|
||||
{!completed && (
|
||||
<IonGrid className={`${styles.mainGrid} animate__animated`} ref={mainContainerRef}>
|
||||
<QuizStats
|
||||
chosenCategory={chosenCategory}
|
||||
chosenDifficulty={chosenDifficulty}
|
||||
questionsLength={questions.length}
|
||||
currentQuestion={currentQuestion}
|
||||
score={score}
|
||||
/>
|
||||
|
||||
<IonRow className={styles.mainRow}>
|
||||
<IonCol size="12">
|
||||
<IonRow>
|
||||
<Swiper
|
||||
ref={swiperRef}
|
||||
spaceBetween={slideSpace}
|
||||
slidesPerView={1}
|
||||
onSlideChange={(e) => setCurrentQuestion(e.activeIndex + 1)}
|
||||
>
|
||||
{questions &&
|
||||
questions.map((question, index) => {
|
||||
return (
|
||||
<SwiperSlide key={`question_${index}`}>
|
||||
<IonCard id="questionCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>{question.category}</IonCardSubtitle>
|
||||
{question.tags.length > 0 && (
|
||||
<IonBadge color="success">{question.tags[0].name}</IonBadge>
|
||||
)}
|
||||
<IonCardTitle className={styles.questionTitle}>
|
||||
{question.question}
|
||||
</IonCardTitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
{Object.keys(question.answers).map((answer, index) => {
|
||||
if (question.answers[answer] !== null) {
|
||||
return (
|
||||
<Answer
|
||||
key={`answer_${index}`}
|
||||
answer={answer}
|
||||
question={question}
|
||||
handleAnswerClick={handleAnswerClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
)}
|
||||
|
||||
{completed && (
|
||||
<CompletedCard
|
||||
completionContainerRef={completionContainerRef}
|
||||
score={score}
|
||||
questionsLength={questions.length}
|
||||
/>
|
||||
)}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Questions;
|
153
03_source/mobile/src/pages/DemoQuizApp/AppPages/Quiz.jsx
Normal file
153
03_source/mobile/src/pages/DemoQuizApp/AppPages/Quiz.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
useIonToast,
|
||||
} from '@ionic/react';
|
||||
|
||||
import styles from './Quiz.module.scss';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { SettingsStore } from '../store';
|
||||
import {
|
||||
getCategories,
|
||||
getChosenCategory,
|
||||
getChosenDifficulty,
|
||||
getDifficulties,
|
||||
} from '../store/Selectors';
|
||||
import { Category, Difficulty } from '../components/Settings';
|
||||
|
||||
const Quiz = () => {
|
||||
const router = useIonRouter();
|
||||
const categories = useStoreState(SettingsStore, getCategories);
|
||||
const difficulties = useStoreState(SettingsStore, getDifficulties);
|
||||
|
||||
const chosenCategory = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
|
||||
const [show, hide] = useIonToast();
|
||||
|
||||
const startQuiz = async () => {
|
||||
if (chosenCategory && chosenDifficulty) {
|
||||
const chosenCategoryElement = document.getElementById(`categoryButton_${chosenCategory}`);
|
||||
const chosenDifficultyElement = document.getElementById(
|
||||
`difficultyButton_${chosenDifficulty}`
|
||||
);
|
||||
|
||||
const categoriesCardElement = document.getElementById('categoriesCard');
|
||||
const difficultiesCardElement = document.getElementById('difficultiesCard');
|
||||
|
||||
chosenCategoryElement.classList.add('ontop');
|
||||
chosenDifficultyElement.classList.add('ontop');
|
||||
|
||||
chosenCategoryElement.classList.add('animate__heartBeat');
|
||||
chosenDifficultyElement.classList.add('animate__heartBeat');
|
||||
|
||||
setTimeout(() => {
|
||||
chosenCategoryElement.classList.remove('animate__heartBeat');
|
||||
chosenDifficultyElement.classList.remove('animate__heartBeat');
|
||||
chosenCategoryElement.classList.remove('ontop');
|
||||
chosenDifficultyElement.classList.remove('ontop');
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
categoriesCardElement.classList.add('animate__slideOutRight');
|
||||
difficultiesCardElement.classList.add('animate__slideOutLeft');
|
||||
|
||||
setTimeout(() => {
|
||||
categoriesCardElement.classList.remove('animate__slideOutRight');
|
||||
difficultiesCardElement.classList.remove('animate__slideOutLeft');
|
||||
}, 1000);
|
||||
}, 1100);
|
||||
|
||||
setTimeout(() => {
|
||||
router.push('/questions');
|
||||
}, 1700);
|
||||
} else {
|
||||
show({
|
||||
header: 'Hang on there!',
|
||||
message: 'You must choose a category and difficulty!',
|
||||
duration: 3000,
|
||||
color: 'warning',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
<img src="/assets/DemoQuizApp/main.png" style={{ width: '30%' }} alt="logo" />
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen className="background">
|
||||
<IonGrid className={styles.mainGrid}>
|
||||
<IonRow className={styles.mainRow}>
|
||||
<IonCol size="12">
|
||||
<IonCard id="categoriesCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>Choose a category</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonRow>
|
||||
{categories.map((category, index) => {
|
||||
const chosen = category.value === chosenCategory;
|
||||
|
||||
return <Category key={`category_${index}`} {...category} chosen={chosen} />;
|
||||
})}
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={styles.difficultyContainer}>
|
||||
<IonCol size="12">
|
||||
<IonCard id="difficultiesCard" className="animate__animated">
|
||||
<IonCardHeader className="ion-text-center">
|
||||
<IonCardSubtitle>Choose a difficulty</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonRow>
|
||||
{difficulties.map((difficulty, index) => {
|
||||
const chosen = difficulty.value === chosenDifficulty;
|
||||
|
||||
return (
|
||||
<Difficulty key={`difficulty_${index}`} {...difficulty} chosen={chosen} />
|
||||
);
|
||||
})}
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<div className={styles.startButton} onClick={startQuiz}>
|
||||
Start Quiz!
|
||||
</div>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Quiz;
|
@@ -0,0 +1,46 @@
|
||||
.difficultyContainer {
|
||||
|
||||
margin-top: -2rem !important;
|
||||
}
|
||||
|
||||
.startButton {
|
||||
|
||||
background-color: #994ec1;
|
||||
padding: 1.25rem;
|
||||
margin: 1rem;
|
||||
margin-top: -1rem;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
border: 2px solid #632485;
|
||||
}
|
||||
|
||||
.questionTitle {
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answerButton {
|
||||
|
||||
height: fit-content;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mainGrid {
|
||||
|
||||
// margin-top: -2rem;
|
||||
}
|
||||
|
||||
.mainRow {
|
||||
|
||||
margin-top: -2rem;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
|
||||
font-size: 4rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
}
|
17
03_source/mobile/src/pages/DemoQuizApp/components/Answer.jsx
Normal file
17
03_source/mobile/src/pages/DemoQuizApp/components/Answer.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IonButton, IonCol, IonRow } from '@ionic/react';
|
||||
import styles from './Quiz.module.scss';
|
||||
|
||||
export const Answer = ({ answer, handleAnswerClick, question }) => (
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
onClick={(e) => handleAnswerClick(e, answer, question)}
|
||||
expand="block"
|
||||
color="light"
|
||||
className={`ion-text-wrap ${styles.answerButton}`}
|
||||
>
|
||||
{question.answers[answer]}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
);
|
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonNote,
|
||||
IonRow,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import styles from './Quiz.module.scss';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
|
||||
export const CompletedCard = ({ completionContainerRef, score, questionsLength }) => {
|
||||
const router = useIonRouter();
|
||||
|
||||
const playAgain = () => {
|
||||
updateChosenCategory(false);
|
||||
updateChosenDifficulty(false);
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<IonGrid className="animate__animated" ref={completionContainerRef}>
|
||||
<IonRow className="ion-text-center">
|
||||
<IonCol size="12">
|
||||
<IonCard>
|
||||
<IonCardHeader>
|
||||
<IonCardSubtitle>Congratulations</IonCardSubtitle>
|
||||
<IonCardTitle>Quiz Complete!</IonCardTitle>
|
||||
<p className={styles.emoji}>🎉</p>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonNote>You scored</IonNote>
|
||||
|
||||
<IonCardTitle className="ion-margin-bottom">
|
||||
{score}/{questionsLength}
|
||||
</IonCardTitle>
|
||||
|
||||
<IonButton
|
||||
onClick={playAgain}
|
||||
color="success"
|
||||
expand="block"
|
||||
className="ion-margin-top"
|
||||
>
|
||||
Play Again!
|
||||
</IonButton>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
);
|
||||
};
|
@@ -0,0 +1,46 @@
|
||||
.difficultyContainer {
|
||||
|
||||
margin-top: -2rem !important;
|
||||
}
|
||||
|
||||
.startButton {
|
||||
|
||||
background-color: #994ec1;
|
||||
padding: 1.25rem;
|
||||
margin: 1rem;
|
||||
margin-top: -1rem;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
border: 2px solid #632485;
|
||||
}
|
||||
|
||||
.questionTitle {
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answerButton {
|
||||
|
||||
height: fit-content;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mainGrid {
|
||||
|
||||
// margin-top: -2rem;
|
||||
}
|
||||
|
||||
.mainRow {
|
||||
|
||||
margin-top: -2rem;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
|
||||
font-size: 4rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { IonCard, IonCardContent, IonCardSubtitle, IonCol, IonItem, IonLabel, IonNote, IonRow } from "@ionic/react";
|
||||
|
||||
export const QuizStats = ({ chosenCategory, chosenDifficulty, currentQuestion, questionsLength, score }) => (
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonCardSubtitle>{ chosenCategory } | { chosenDifficulty }</IonCardSubtitle>
|
||||
<IonItem lines="none">
|
||||
<IonLabel className="ion-text-center">
|
||||
<IonCardSubtitle>Question</IonCardSubtitle>
|
||||
<IonNote>{ currentQuestion } / { questionsLength }</IonNote>
|
||||
</IonLabel>
|
||||
|
||||
<IonLabel className="ion-text-center">
|
||||
<IonCardSubtitle>Score</IonCardSubtitle>
|
||||
<IonNote>{ score }</IonNote>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
);
|
@@ -0,0 +1,17 @@
|
||||
import { IonCol } from "@ionic/react";
|
||||
import { updateChosenCategory, updateChosenDifficulty } from "../store/SettingsStore";
|
||||
import styles from "./Settings.module.scss";
|
||||
|
||||
export const Category = ({ label, value, set, chosen }) => (
|
||||
|
||||
<IonCol id={ `categoryButton_${ value }` } size="6" className={ `${styles.category} ${chosen && styles.chosen} animate__animated` } onClick={ () => updateChosenCategory(value) }>
|
||||
<p>{ label }</p>
|
||||
</IonCol>
|
||||
);
|
||||
|
||||
export const Difficulty = ({ label, value, set, chosen }) => (
|
||||
|
||||
<IonCol id={ `difficultyButton_${ value }` } size="4" className={ `${ styles.category } ${ chosen && styles.chosen } animate__animated` } onClick={ () => updateChosenDifficulty(value) }>
|
||||
<p>{ label }</p>
|
||||
</IonCol>
|
||||
);
|
@@ -0,0 +1,20 @@
|
||||
.category {
|
||||
|
||||
height: 4rem;
|
||||
border: 5px solid rgb(255, 255, 255);
|
||||
background-color: #994ec1;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.chosen {
|
||||
|
||||
border: 2px solid #3a1d49;
|
||||
font-weight: 700;
|
||||
}
|
33
03_source/mobile/src/pages/DemoQuizApp/index.tsx
Normal file
33
03_source/mobile/src/pages/DemoQuizApp/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IonRouterOutlet, IonTabs } from '@ionic/react';
|
||||
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Home from './AppPages/Home';
|
||||
import Quiz from './AppPages/Quiz';
|
||||
import Questions from './AppPages/Questions';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoQuizApp() {
|
||||
return (
|
||||
<IonTabs className="demo-quiz-app">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-quiz-app/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-quiz-app/quiz">
|
||||
<Quiz />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-quiz-app/questions">
|
||||
<Questions />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-quiz-app" to="/demo-quiz-app/home" />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoQuizApp;
|
11
03_source/mobile/src/pages/DemoQuizApp/questions/index.js
Normal file
11
03_source/mobile/src/pages/DemoQuizApp/questions/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const API_URL = 'https://quizapi.io/api/v1/questions';
|
||||
const API_KEY = 'B27jnk1wmfEOQ42FtmrgBogiNTLLhOArJj29y24a';
|
||||
|
||||
export const fetchQuestions = async (category, difficulty) => {
|
||||
const response = await fetch(
|
||||
`${API_URL}?apiKey=${API_KEY}&category=${category}&difficulty=${difficulty}&limit=10`
|
||||
);
|
||||
const questions = await response.json();
|
||||
|
||||
return questions;
|
||||
};
|
10
03_source/mobile/src/pages/DemoQuizApp/store/Selectors.js
Normal file
10
03_source/mobile/src/pages/DemoQuizApp/store/Selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const getState = state => state;
|
||||
|
||||
// Getters
|
||||
export const getCategories = createSelector(getState, state => state.categories);
|
||||
export const getDifficulties = createSelector(getState, state => state.difficulties);
|
||||
|
||||
export const getChosenCategory = createSelector(getState, state => state.chosenCategory);
|
||||
export const getChosenDifficulty = createSelector(getState, state => state.chosenDifficulty);
|
@@ -0,0 +1,60 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const SettingsStore = new Store({
|
||||
|
||||
categories: [
|
||||
{
|
||||
label: "Code",
|
||||
value: "code",
|
||||
},
|
||||
{
|
||||
label: "Linux",
|
||||
value: "linux"
|
||||
},
|
||||
{
|
||||
label: "Dev Ops",
|
||||
value: "devops"
|
||||
},
|
||||
{
|
||||
label: "Authentication",
|
||||
value: "authentication"
|
||||
},
|
||||
{
|
||||
label: "Bash",
|
||||
value: "bash"
|
||||
},
|
||||
{
|
||||
label: "SQL",
|
||||
value: "sql"
|
||||
}
|
||||
],
|
||||
difficulties: [
|
||||
{
|
||||
label: "Easy",
|
||||
value: "easy"
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium"
|
||||
},
|
||||
{
|
||||
label: "Hard",
|
||||
value: "hard"
|
||||
}
|
||||
],
|
||||
|
||||
chosenCategory: false,
|
||||
chosenDifficulty: false
|
||||
});
|
||||
|
||||
export default SettingsStore;
|
||||
|
||||
export const updateChosenCategory = category => {
|
||||
|
||||
SettingsStore.update(s => { s.chosenCategory = category });
|
||||
}
|
||||
|
||||
export const updateChosenDifficulty = difficulty => {
|
||||
|
||||
SettingsStore.update(s => { s.chosenDifficulty = difficulty });
|
||||
}
|
1
03_source/mobile/src/pages/DemoQuizApp/store/index.js
Normal file
1
03_source/mobile/src/pages/DemoQuizApp/store/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SettingsStore } from "./SettingsStore";
|
113
03_source/mobile/src/pages/DemoQuizApp/style.scss
Normal file
113
03_source/mobile/src/pages/DemoQuizApp/style.scss
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user