Update requirement files with new feature templates and fix backend API error message, along with mobile project config updates and documentation improvements
This commit is contained in:
@@ -14,9 +14,10 @@ import {
|
||||
import styles from './Home.module.scss';
|
||||
|
||||
import { informationCircleOutline } from 'ionicons/icons';
|
||||
import React from 'react';
|
||||
|
||||
const Home = () => {
|
||||
const [show, hide] = useIonActionSheet();
|
||||
const Home: React.FC = () => {
|
||||
const [show, hide]: [any, any] = useIonActionSheet();
|
||||
|
||||
return (
|
||||
<IonPage>
|
@@ -50,21 +50,21 @@ import { Answer } from '../components/Answer';
|
||||
import { CompletedCard } from '../components/CompletedCard';
|
||||
import { QuizStats } from '../components/QuizStats';
|
||||
|
||||
const Questions = () => {
|
||||
const mainContainerRef = useRef(null);
|
||||
const completionContainerRef = useRef(null);
|
||||
const swiperRef = useRef(null);
|
||||
const Questions: React.FC = () => {
|
||||
const mainContainerRef = useRef<any>(null);
|
||||
const completionContainerRef = useRef<any>(null);
|
||||
const swiperRef = useRef<any>(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 [currentQuestion, setCurrentQuestion] = useState<number>(1);
|
||||
const [score, setScore] = useState<number>(0);
|
||||
const [completed, setCompleted] = useState<boolean>(false);
|
||||
|
||||
const [questions, setQuestions] = useState(false);
|
||||
const [slideSpace, setSlideSpace] = useState(0);
|
||||
const [questions, setQuestions] = useState<any>(false);
|
||||
const [slideSpace, setSlideSpace] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getQuestions = async () => {
|
||||
@@ -79,7 +79,7 @@ const Questions = () => {
|
||||
setSlideSpace(40);
|
||||
});
|
||||
|
||||
const handleAnswerClick = (event, answer, question) => {
|
||||
const handleAnswerClick = (event: any, answer: any, question: any) => {
|
||||
const isCorrect = question.correct_answers[`${answer}_correct`] === 'true';
|
||||
|
||||
if (isCorrect) {
|
||||
@@ -136,10 +136,10 @@ const Questions = () => {
|
||||
ref={swiperRef}
|
||||
spaceBetween={slideSpace}
|
||||
slidesPerView={1}
|
||||
onSlideChange={(e) => setCurrentQuestion(e.activeIndex + 1)}
|
||||
onSlideChange={(e: any) => setCurrentQuestion(e.activeIndex + 1)}
|
||||
>
|
||||
{questions &&
|
||||
questions.map((question, index) => {
|
||||
questions.map((question: any, index: number) => {
|
||||
return (
|
||||
<SwiperSlide key={`question_${index}`}>
|
||||
<IonCard id="questionCard" className="animate__animated">
|
||||
@@ -154,7 +154,7 @@ const Questions = () => {
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
{Object.keys(question.answers).map((answer, index) => {
|
||||
{Object.keys(question.answers).map((answer: any, index: number) => {
|
||||
if (question.answers[answer] !== null) {
|
||||
return (
|
||||
<Answer
|
@@ -28,17 +28,17 @@ import {
|
||||
} from '../store/Selectors';
|
||||
import { Category, Difficulty } from '../components/Settings';
|
||||
|
||||
const Quiz = () => {
|
||||
const Quiz: React.FC = () => {
|
||||
const router = useIonRouter();
|
||||
const categories = useStoreState(SettingsStore, getCategories);
|
||||
const difficulties = useStoreState(SettingsStore, getDifficulties);
|
||||
const categories: any[] = useStoreState(SettingsStore, getCategories);
|
||||
const difficulties: any[] = useStoreState(SettingsStore, getDifficulties);
|
||||
|
||||
const chosenCategory = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
const chosenCategory: any = useStoreState(SettingsStore, getChosenCategory);
|
||||
const chosenDifficulty: any = useStoreState(SettingsStore, getChosenDifficulty);
|
||||
|
||||
const [show, hide] = useIonToast();
|
||||
const [show, hide]: [any, any] = useIonToast();
|
||||
|
||||
const startQuiz = async () => {
|
||||
const startQuiz = async (): Promise<void> => {
|
||||
if (chosenCategory && chosenDifficulty) {
|
||||
const chosenCategoryElement = document.getElementById(`categoryButton_${chosenCategory}`);
|
||||
const chosenDifficultyElement = document.getElementById(
|
||||
@@ -48,6 +48,16 @@ const Quiz = () => {
|
||||
const categoriesCardElement = document.getElementById('categoriesCard');
|
||||
const difficultiesCardElement = document.getElementById('difficultiesCard');
|
||||
|
||||
if (
|
||||
!chosenCategoryElement ||
|
||||
!chosenDifficultyElement ||
|
||||
!categoriesCardElement ||
|
||||
!difficultiesCardElement
|
||||
) {
|
||||
console.error('Could not find required DOM elements');
|
||||
return;
|
||||
}
|
||||
|
||||
chosenCategoryElement.classList.add('ontop');
|
||||
chosenDifficultyElement.classList.add('ontop');
|
||||
|
@@ -1,11 +1,15 @@
|
||||
import { IonButton, IonCol, IonRow } from '@ionic/react';
|
||||
import styles from './Quiz.module.scss';
|
||||
|
||||
export const Answer = ({ answer, handleAnswerClick, question }) => (
|
||||
export const Answer: React.FC<{
|
||||
answer: any;
|
||||
handleAnswerClick: any;
|
||||
question: any;
|
||||
}> = ({ answer, handleAnswerClick, question }) => (
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
onClick={(e) => handleAnswerClick(e, answer, question)}
|
||||
onClick={(e: any) => handleAnswerClick(e, answer, question)}
|
||||
expand="block"
|
||||
color="light"
|
||||
className={`ion-text-wrap ${styles.answerButton}`}
|
@@ -14,10 +14,14 @@ import {
|
||||
import styles from './Quiz.module.scss';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
|
||||
export const CompletedCard = ({ completionContainerRef, score, questionsLength }) => {
|
||||
export const CompletedCard: React.FC<{
|
||||
completionContainerRef: any;
|
||||
score: number;
|
||||
questionsLength: number;
|
||||
}> = ({ completionContainerRef, score, questionsLength }) => {
|
||||
const router = useIonRouter();
|
||||
|
||||
const playAgain = () => {
|
||||
const playAgain = (): void => {
|
||||
updateChosenCategory(false);
|
||||
updateChosenDifficulty(false);
|
||||
router.push('/');
|
@@ -1,25 +0,0 @@
|
||||
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,43 @@
|
||||
import {
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardSubtitle,
|
||||
IonCol,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonRow,
|
||||
} from '@ionic/react';
|
||||
|
||||
export const QuizStats: React.FC<{
|
||||
chosenCategory: string;
|
||||
chosenDifficulty: string;
|
||||
currentQuestion: number;
|
||||
questionsLength: number;
|
||||
score: number;
|
||||
}> = ({ 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>
|
||||
);
|
@@ -1,17 +0,0 @@
|
||||
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,35 @@
|
||||
import { IonCol } from '@ionic/react';
|
||||
import { updateChosenCategory, updateChosenDifficulty } from '../store/SettingsStore';
|
||||
import styles from './Settings.module.scss';
|
||||
|
||||
export const Category: React.FC<{ label: string; value: any; set: any; chosen: any }> = ({
|
||||
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: React.FC<{ label: string; value: any; set: any; chosen: any }> = ({
|
||||
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>
|
||||
);
|
@@ -8,9 +8,9 @@ import Questions from './AppPages/Questions';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoQuizApp() {
|
||||
const DemoQuizApp: React.FC = () => {
|
||||
return (
|
||||
<IonTabs className="demo-quiz-app">
|
||||
<div className="demo-quiz-app">
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-quiz-app/home">
|
||||
<Home />
|
||||
@@ -26,8 +26,8 @@ function DemoQuizApp() {
|
||||
|
||||
<Redirect exact path="/demo-quiz-app" to="/demo-quiz-app/home" />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default DemoQuizApp;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const API_URL = 'https://quizapi.io/api/v1/questions';
|
||||
const API_KEY = 'B27jnk1wmfEOQ42FtmrgBogiNTLLhOArJj29y24a';
|
||||
|
||||
export const fetchQuestions = async (category, difficulty) => {
|
||||
export const fetchQuestions = async (category: string, difficulty: string): Promise<any[]> => {
|
||||
const response = await fetch(
|
||||
`${API_URL}?apiKey=${API_KEY}&category=${category}&difficulty=${difficulty}&limit=10`
|
||||
);
|
@@ -1,10 +0,0 @@
|
||||
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);
|
10
03_source/mobile/src/pages/DemoQuizApp/store/Selectors.ts
Normal file
10
03_source/mobile/src/pages/DemoQuizApp/store/Selectors.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const getState = (state: any) => state;
|
||||
|
||||
// Getters
|
||||
export const getCategories = createSelector(getState, (state: any) => state.categories);
|
||||
export const getDifficulties = createSelector(getState, (state: any) => state.difficulties);
|
||||
|
||||
export const getChosenCategory = createSelector(getState, (state: any) => state.chosenCategory);
|
||||
export const getChosenDifficulty = createSelector(getState, (state: any) => state.chosenDifficulty);
|
@@ -1,60 +0,0 @@
|
||||
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 });
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import { Store } from 'pullstate';
|
||||
|
||||
const SettingsStore = new Store<any>({
|
||||
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: any) => {
|
||||
SettingsStore.update((s: any) => {
|
||||
s.chosenCategory = category;
|
||||
});
|
||||
};
|
||||
|
||||
export const updateChosenDifficulty = (difficulty: any) => {
|
||||
SettingsStore.update((s: any) => {
|
||||
s.chosenDifficulty = difficulty;
|
||||
});
|
||||
};
|
@@ -1 +0,0 @@
|
||||
export { default as SettingsStore } from "./SettingsStore";
|
1
03_source/mobile/src/pages/DemoQuizApp/store/index.ts
Normal file
1
03_source/mobile/src/pages/DemoQuizApp/store/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SettingsStore } from './SettingsStore';
|
Reference in New Issue
Block a user