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; text-align: center;
margin-bottom: 2rem; margin-bottom: 2rem;
@media (max-width: 768px) { @media (max-width: 768px) {
font-size: 2rem; font-size: 1.4rem;
margin-bottom: .5rem;
} }
} }
@@ -50,13 +51,14 @@ export const Wrapper = styled.div`
} }
.details { .details {
margin: 2rem 0; margin: .3rem 0;
font-size: 1.4rem; font-size: 1.4rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
@media (max-width: 768px) { @media (max-width: 768px) {
flex-direction: column; flex-direction: column;
font-size: 1.2rem;
} }
} }
@@ -109,7 +111,7 @@ export const ButtonWrapper = styled.div<ButtonWrapperProps>`
export const SelectWrapper = styled.select` export const SelectWrapper = styled.select`
width: 100%; width: 100%;
max-width: 70rem; max-width: 70rem;
font-size: 1.6rem; font-size: 1.4rem;
background-color: #22363c; background-color: #22363c;
color: white; color: white;
padding: 0.24rem; padding: 0.24rem;
+35 -29
View File
@@ -5,25 +5,26 @@ import { GlobalStyle, Wrapper } from './App.style';
import SelectDifficulty, { Difficulty } from './components/SelectDifficulty'; import SelectDifficulty, { Difficulty } from './components/SelectDifficulty';
import SelectQuestions, { TotalQuestions } from './components/SelectQuestions'; import SelectQuestions, { TotalQuestions } from './components/SelectQuestions';
import SelectCategory from './components/SelectCategory'; import SelectCategory from './components/SelectCategory';
import DetailsCard from './components/DetailsCard';
import { Category, QuestionState, AnswerObject } from './Types'; import { Category, QuestionState, AnswerObject } from './Types';
const App = () => { const App = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [questions, setQuestions] = useState<QuestionState[]>([]); const [questions, setQuestions] = useState<QuestionState[]>([]);
const [number, setNumber] = useState(0); const [questionNumber, setQuestionNumber] = useState(0);
const [userAnswers, setUserAnswers] = useState<AnswerObject[]>([]); const [userAnswers, setUserAnswers] = useState<AnswerObject[]>([]);
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(true); const [gameOver, setGameOver] = useState<boolean>(true);
const [difficulty, setDifficulty] = useState<string>(Difficulty.EASY); const [difficulty, setDifficulty] = useState<string>(Difficulty.EASY);
const [totalQuestionNumber, setTotalQuestionNumber] = useState<number>( const [totalQuestionNumber, setTotalQuestionNumber] = useState<number>(
TotalQuestions.TEN TotalQuestions.TEN
); );
const [category, setCategory] = useState<number>(1); const [categoryID, setCategoryID] = useState<number>(1);
const [categoryList, setCategoryList] = useState<Array<Category>>([]); const [categoryList, setCategoryList] = useState<Array<Category>>([]);
useEffect(() => { useEffect(() => {
fetchCategories().then((data) => setCategoryList(data)); fetchCategories().then((categories) => setCategoryList(categories));
}, []); }, []);
const startTrivia = async () => { const startTrivia = async () => {
@@ -32,14 +33,14 @@ const App = () => {
const newQuestions = await fetchQuizQuestions( const newQuestions = await fetchQuizQuestions(
totalQuestionNumber, totalQuestionNumber,
category, categoryID,
difficulty difficulty
); );
setQuestions(newQuestions);
setScore(0); setScore(0);
setUserAnswers([]); setUserAnswers([]);
setNumber(0); setQuestionNumber(0);
setQuestions(newQuestions);
setLoading(false); setLoading(false);
}; };
@@ -47,17 +48,16 @@ const App = () => {
if (!gameOver) { if (!gameOver) {
const answer = e.currentTarget.value; const answer = e.currentTarget.value;
const correct = questions[number].correct_answer === answer; const correct = questions[questionNumber].correct_answer === answer;
if (correct) { if (correct) {
setScore((prev) => prev + 1); setScore((prev) => prev + 1);
} }
const answerObject = { const answerObject = {
question: questions[number].question, question: questions[questionNumber].question,
answer, answer,
correct, correct,
correctAnswer: questions[number].correct_answer correctAnswer: questions[questionNumber].correct_answer
}; };
setUserAnswers((prev) => [...prev, answerObject]); setUserAnswers((prev) => [...prev, answerObject]);
@@ -65,11 +65,10 @@ const App = () => {
}; };
const nextQuestion = () => { const nextQuestion = () => {
const nextNumber = number + 1; if (questionNumber + 1 >= totalQuestionNumber) {
if (nextNumber === totalQuestionNumber) {
setGameOver(true); setGameOver(true);
} else { } else {
setNumber(nextNumber); setQuestionNumber(questionNumber + 1);
} }
}; };
@@ -79,7 +78,7 @@ const App = () => {
<Wrapper className="App"> <Wrapper className="App">
<h1>Quiz</h1> <h1>Quiz</h1>
{gameOver || number + 1 === totalQuestionNumber ? ( {gameOver || questionNumber === totalQuestionNumber ? (
<div> <div>
<SelectDifficulty <SelectDifficulty
difficulty={difficulty} difficulty={difficulty}
@@ -90,37 +89,44 @@ const App = () => {
setTotalQuestionNumber={setTotalQuestionNumber} setTotalQuestionNumber={setTotalQuestionNumber}
/> />
<SelectCategory <SelectCategory
category={category} categoryID={categoryID}
categoryList={categoryList} categoryList={categoryList}
setCategory={setCategory} setCategoryID={setCategoryID}
/> />
<button className="start" onClick={startTrivia}> <button className="start" onClick={startTrivia}>
Start Start
{number + 1 === totalQuestionNumber && ' again'} {questionNumber + 1 === totalQuestionNumber && ' again'}
</button> </button>
</div> </div>
) : null} ) : 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 && ( {!loading && !gameOver && (
<QuestionCard <QuestionCard
difficulty={difficulty} question={questions[questionNumber].question}
score={score} answers={questions[questionNumber].answers}
questionNr={number + 1} userAnswers={userAnswers ? userAnswers[questionNumber] : undefined}
totalQuestions={totalQuestionNumber}
question={questions[number].question}
answers={questions[number].answers}
category={questions[number].category}
userAnswer={userAnswers ? userAnswers[number] : undefined}
callback={checkAnswer} callback={checkAnswer}
/> />
)} )}
{!gameOver && {!gameOver &&
!loading && !loading &&
userAnswers.length === number + 1 && userAnswers.length === questionNumber + 1 &&
number + 1 !== totalQuestionNumber ? ( questionNumber !== totalQuestionNumber ? (
<button className="next" onClick={nextQuestion}> <button className="next" onClick={nextQuestion}>
Next Question Next Question
</button> </button>
+15
View File
@@ -9,3 +9,18 @@ export const Capitalize = (text: string) => {
(_, m1, m2) => m1.toUpperCase() + m2.toLowerCase() (_, 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;
+8 -36
View File
@@ -1,64 +1,36 @@
import React from 'react'; import React from 'react';
import { ButtonWrapper } from '../App.style'; import { ButtonWrapper } from '../App.style';
import { AnswerObject } from '../Types'; import { AnswerObject } from '../Types';
import { Capitalize } from '../Utils'; import { unEscape } from '../Utils';
type Props = { type Props = {
answers: string[]; answers: string[];
callback: (e: React.MouseEvent<HTMLButtonElement>) => void; callback: (e: React.MouseEvent<HTMLButtonElement>) => void;
difficulty: string;
question: string; question: string;
category: string; userAnswers: AnswerObject | undefined;
questionNr: number;
score: number;
totalQuestions: number;
userAnswer: AnswerObject | undefined;
}; };
const QuestionCard: React.FC<Props> = ({ const QuestionCard: React.FC<Props> = ({
answers, answers,
callback, callback,
difficulty,
question, question,
category, userAnswers
questionNr,
score,
totalQuestions,
userAnswer
}) => ( }) => (
<div className="question-card-wrapper"> <div className="question-card-wrapper">
<div className="details"> <p className="question">{unEscape(question)}</p>
<div>Difficulty: {Capitalize(difficulty)}</div>
<div>{category}</div>
</div>
<div className="details">
<div>
Question: {questionNr}/{totalQuestions}
</div>
<div>Score: {score}</div>
</div>
{!(questionNr === totalQuestions) && (
<>
<p
className="question"
dangerouslySetInnerHTML={{ __html: question }}
/>
<div> <div>
{answers.map((answer) => ( {answers.map((answer) => (
<ButtonWrapper <ButtonWrapper
correct={userAnswer?.correctAnswer === answer} correct={userAnswers?.correctAnswer === answer}
userClicked={userAnswer?.answer === answer} userClicked={userAnswers?.answer === answer}
key={answer} key={answer}
> >
<button disabled={!!userAnswer} onClick={callback} value={answer}> <button disabled={!!userAnswers} onClick={callback} value={answer}>
<span dangerouslySetInnerHTML={{ __html: answer }} /> <span>{unEscape(answer)}</span>
</button> </button>
</ButtonWrapper> </ButtonWrapper>
))} ))}
</div> </div>
</>
)}
</div> </div>
); );
+9 -9
View File
@@ -4,24 +4,24 @@ import { SelectWrapper } from '../App.style';
import { Category } from '../Types'; import { Category } from '../Types';
type Props = { type Props = {
category: number; categoryID: number;
categoryList: Array<Category>; categoryList: Array<Category>;
setCategory: (category: number) => void; setCategoryID: (categoryID: number) => void;
}; };
const SelectCategory = ({ category, categoryList, setCategory }: Props) => { const SelectCategory = ({ categoryID, categoryList, setCategoryID }: Props) => {
return ( return (
<SelectWrapper <SelectWrapper
value={category} value={categoryID}
onChange={(e: React.ChangeEvent<{ value: unknown }>) => { onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
e.preventDefault(); e.preventDefault();
setCategory(e.target.value as number); setCategoryID(e.target.value as number);
}} }}
> >
<option value={1}>Any Category</option> <option value={1}>Random Category</option>
{categoryList.map((cat) => ( {categoryList.map((category) => (
<option value={cat.id} key={cat.id}> <option value={category.id} key={category.id}>
{cat.name} {category.name.replace(/Entertainment: |Science: /g, '')}
</option> </option>
))} ))}
</SelectWrapper> </SelectWrapper>
+3
View File
@@ -2,12 +2,15 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
import ErrorBoundary from './components/ErrorBoundary';
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement
); );
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary>
<App /> <App />
</ErrorBoundary>
</React.StrictMode> </React.StrictMode>
); );