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:
louiscklaw
2025-06-13 12:11:47 +08:00
parent f23a6b7d9c
commit 346992d4ec
3102 changed files with 220182 additions and 2896 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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');

View File

@@ -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}`}

View File

@@ -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('/');

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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`
);

View File

@@ -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);

View 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);

View File

@@ -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 });
}

View File

@@ -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;
});
};

View File

@@ -1 +0,0 @@
export { default as SettingsStore } from "./SettingsStore";

View File

@@ -0,0 +1 @@
export { default as SettingsStore } from './SettingsStore';