added error boundary, fixed selection and layout

This commit is contained in:
2023-06-03 19:52:15 +02:00
parent 4114ccb678
commit bd49129273
8 changed files with 157 additions and 86 deletions
+5 -3
View File
@@ -41,7 +41,8 @@ export const Wrapper = styled.div`
text-align: center;
margin-bottom: 2rem;
@media (max-width: 768px) {
font-size: 2rem;
font-size: 1.4rem;
margin-bottom: .5rem;
}
}
@@ -50,13 +51,14 @@ export const Wrapper = styled.div`
}
.details {
margin: 2rem 0;
margin: .3rem 0;
font-size: 1.4rem;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
@media (max-width: 768px) {
flex-direction: column;
font-size: 1.2rem;
}
}
@@ -109,7 +111,7 @@ export const ButtonWrapper = styled.div<ButtonWrapperProps>`
export const SelectWrapper = styled.select`
width: 100%;
max-width: 70rem;
font-size: 1.6rem;
font-size: 1.4rem;
background-color: #22363c;
color: white;
padding: 0.24rem;
+35 -29
View File
@@ -5,25 +5,26 @@ import { GlobalStyle, Wrapper } from './App.style';
import SelectDifficulty, { Difficulty } from './components/SelectDifficulty';
import SelectQuestions, { TotalQuestions } from './components/SelectQuestions';
import SelectCategory from './components/SelectCategory';
import DetailsCard from './components/DetailsCard';
import { Category, QuestionState, AnswerObject } from './Types';
const App = () => {
const [loading, setLoading] = useState(false);
const [questions, setQuestions] = useState<QuestionState[]>([]);
const [number, setNumber] = useState(0);
const [questionNumber, setQuestionNumber] = useState(0);
const [userAnswers, setUserAnswers] = useState<AnswerObject[]>([]);
const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(true);
const [gameOver, setGameOver] = useState<boolean>(true);
const [difficulty, setDifficulty] = useState<string>(Difficulty.EASY);
const [totalQuestionNumber, setTotalQuestionNumber] = useState<number>(
TotalQuestions.TEN
);
const [category, setCategory] = useState<number>(1);
const [categoryID, setCategoryID] = useState<number>(1);
const [categoryList, setCategoryList] = useState<Array<Category>>([]);
useEffect(() => {
fetchCategories().then((data) => setCategoryList(data));
fetchCategories().then((categories) => setCategoryList(categories));
}, []);
const startTrivia = async () => {
@@ -32,14 +33,14 @@ const App = () => {
const newQuestions = await fetchQuizQuestions(
totalQuestionNumber,
category,
categoryID,
difficulty
);
setQuestions(newQuestions);
setScore(0);
setUserAnswers([]);
setNumber(0);
setQuestionNumber(0);
setQuestions(newQuestions);
setLoading(false);
};
@@ -47,17 +48,16 @@ const App = () => {
if (!gameOver) {
const answer = e.currentTarget.value;
const correct = questions[number].correct_answer === answer;
const correct = questions[questionNumber].correct_answer === answer;
if (correct) {
setScore((prev) => prev + 1);
}
const answerObject = {
question: questions[number].question,
question: questions[questionNumber].question,
answer,
correct,
correctAnswer: questions[number].correct_answer
correctAnswer: questions[questionNumber].correct_answer
};
setUserAnswers((prev) => [...prev, answerObject]);
@@ -65,11 +65,10 @@ const App = () => {
};
const nextQuestion = () => {
const nextNumber = number + 1;
if (nextNumber === totalQuestionNumber) {
if (questionNumber + 1 >= totalQuestionNumber) {
setGameOver(true);
} else {
setNumber(nextNumber);
setQuestionNumber(questionNumber + 1);
}
};
@@ -79,7 +78,7 @@ const App = () => {
<Wrapper className="App">
<h1>Quiz</h1>
{gameOver || number + 1 === totalQuestionNumber ? (
{gameOver || questionNumber === totalQuestionNumber ? (
<div>
<SelectDifficulty
difficulty={difficulty}
@@ -90,37 +89,44 @@ const App = () => {
setTotalQuestionNumber={setTotalQuestionNumber}
/>
<SelectCategory
category={category}
categoryID={categoryID}
categoryList={categoryList}
setCategory={setCategory}
setCategoryID={setCategoryID}
/>
<button className="start" onClick={startTrivia}>
Start
{number + 1 === totalQuestionNumber && ' again'}
{questionNumber + 1 === totalQuestionNumber && ' again'}
</button>
</div>
) : null}
{loading ?? <p>Load Questions...</p>}
{loading ?? <p>Loading Questions...</p>}
{
<DetailsCard
gameOver={gameOver}
difficulty={difficulty}
category={
questions ? questions[questionNumber]?.category : undefined
}
score={score}
questionNumber={questionNumber}
totalQuestions={totalQuestionNumber}
/>
}
{!loading && !gameOver && (
<QuestionCard
difficulty={difficulty}
score={score}
questionNr={number + 1}
totalQuestions={totalQuestionNumber}
question={questions[number].question}
answers={questions[number].answers}
category={questions[number].category}
userAnswer={userAnswers ? userAnswers[number] : undefined}
question={questions[questionNumber].question}
answers={questions[questionNumber].answers}
userAnswers={userAnswers ? userAnswers[questionNumber] : undefined}
callback={checkAnswer}
/>
)}
{!gameOver &&
!loading &&
userAnswers.length === number + 1 &&
number + 1 !== totalQuestionNumber ? (
userAnswers.length === questionNumber + 1 &&
questionNumber !== totalQuestionNumber ? (
<button className="next" onClick={nextQuestion}>
Next Question
</button>
+15
View File
@@ -9,3 +9,18 @@ export const Capitalize = (text: string) => {
(_, m1, m2) => m1.toUpperCase() + m2.toLowerCase()
);
};
export const unEscape = (text: string) => {
text = text.replace(/&#039;/g, "'");
text = text.replace(/&aacute;/g, 'á');
text = text.replace(/&amp;/g, '&');
text = text.replace(/&aring;/g, 'å');
text = text.replace(/&auml;/g, 'ä');
text = text.replace(/&gt;/g, '>');
text = text.replace(/&lt;/g, '<');
text = text.replace(/&ntilde;/g, 'ñ');
text = text.replace(/&ouml;/g, 'ö');
text = text.replace(/&quot;/g, '"');
text = text.replace(/&uuml;/g, 'ü');
return text;
};
+39
View File
@@ -0,0 +1,39 @@
import React from 'react';
import { Capitalize } from '../Utils';
type Props = {
difficulty: string;
category: string | undefined;
questionNumber: number;
totalQuestions: number;
score: number;
gameOver:boolean;
};
const DetailsCard: React.FC<Props> = ({
difficulty,
category,
questionNumber,
totalQuestions,
score,
gameOver
}) => {
return (
<div className="question-card-wrapper">
<div className="details">
<div>Difficulty: {Capitalize(difficulty)}</div>
<div>
Question: {!gameOver && questionNumber + 1+'\/'}{totalQuestions}
</div>
<div>Score: {score}</div>
{category && (
<div>
Category: {category.replace(/Entertainment: |Science: /g, '')}
</div>
)}
</div>
</div>
);
};
export default DetailsCard;
+34
View File
@@ -0,0 +1,34 @@
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props {
children?: ReactNode;
}
interface State {
hasError: boolean;
}
class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false
};
public static getDerivedStateFromError(_: Error): State {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo);
}
public render() {
if (this.state.hasError) {
return <h1>Sorry.. there was an error</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
+16 -44
View File
@@ -1,64 +1,36 @@
import React from 'react';
import { ButtonWrapper } from '../App.style';
import { AnswerObject } from '../Types';
import { Capitalize } from '../Utils';
import { unEscape } from '../Utils';
type Props = {
answers: string[];
callback: (e: React.MouseEvent<HTMLButtonElement>) => void;
difficulty: string;
question: string;
category: string;
questionNr: number;
score: number;
totalQuestions: number;
userAnswer: AnswerObject | undefined;
userAnswers: AnswerObject | undefined;
};
const QuestionCard: React.FC<Props> = ({
answers,
callback,
difficulty,
question,
category,
questionNr,
score,
totalQuestions,
userAnswer
userAnswers
}) => (
<div className="question-card-wrapper">
<div className="details">
<div>Difficulty: {Capitalize(difficulty)}</div>
<div>{category}</div>
<p className="question">{unEscape(question)}</p>
<div>
{answers.map((answer) => (
<ButtonWrapper
correct={userAnswers?.correctAnswer === answer}
userClicked={userAnswers?.answer === answer}
key={answer}
>
<button disabled={!!userAnswers} onClick={callback} value={answer}>
<span>{unEscape(answer)}</span>
</button>
</ButtonWrapper>
))}
</div>
<div className="details">
<div>
Question: {questionNr}/{totalQuestions}
</div>
<div>Score: {score}</div>
</div>
{!(questionNr === totalQuestions) && (
<>
<p
className="question"
dangerouslySetInnerHTML={{ __html: question }}
/>
<div>
{answers.map((answer) => (
<ButtonWrapper
correct={userAnswer?.correctAnswer === answer}
userClicked={userAnswer?.answer === answer}
key={answer}
>
<button disabled={!!userAnswer} onClick={callback} value={answer}>
<span dangerouslySetInnerHTML={{ __html: answer }} />
</button>
</ButtonWrapper>
))}
</div>
</>
)}
</div>
);
+9 -9
View File
@@ -4,24 +4,24 @@ import { SelectWrapper } from '../App.style';
import { Category } from '../Types';
type Props = {
category: number;
categoryID: number;
categoryList: Array<Category>;
setCategory: (category: number) => void;
setCategoryID: (categoryID: number) => void;
};
const SelectCategory = ({ category, categoryList, setCategory }: Props) => {
const SelectCategory = ({ categoryID, categoryList, setCategoryID }: Props) => {
return (
<SelectWrapper
value={category}
value={categoryID}
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
e.preventDefault();
setCategory(e.target.value as number);
setCategoryID(e.target.value as number);
}}
>
<option value={1}>Any Category</option>
{categoryList.map((cat) => (
<option value={cat.id} key={cat.id}>
{cat.name}
<option value={1}>Random Category</option>
{categoryList.map((category) => (
<option value={category.id} key={category.id}>
{category.name.replace(/Entertainment: |Science: /g, '')}
</option>
))}
</SelectWrapper>
+4 -1
View File
@@ -2,12 +2,15 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import ErrorBoundary from './components/ErrorBoundary';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>
);