Skip to content

Commit a9672bb

Browse files
committed
Completed exercise jonasschmedtmann#13: Workout Timer in section 19 of the course
2 parents 9ceb853 + 7b4d12a commit a9672bb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+18661
-34629
lines changed

10-react-quiz/starter/package-lock.json

Lines changed: 1272 additions & 476 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

10-react-quiz/starter/src (2)/App.css

Whitespace-only changes.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { useEffect, useReducer } from "react";
2+
import Header from "./components/Header/Header";
3+
import Main from "./components/Main/Main";
4+
import Loader from "./components/Loader";
5+
import Error from "./components/Error";
6+
import StartScreen from "./components/Main/StartScreen";
7+
import Questions from "./components/Main/Questions";
8+
import NextButton from "./components/Main/NextButton";
9+
import Progress from "./components/Main/Progress";
10+
import FinishScreen from "./components/Main/FinishScreen";
11+
import Footer from "./components/Main/Footer";
12+
import Timer from "./components/Main/Timer";
13+
14+
const SECS_PER_QUESTION = 30;
15+
16+
const initialState = {
17+
questions: [],
18+
19+
// "loading", "error", "ready", "active", "finished"
20+
status: "loading",
21+
index: 0,
22+
answer: null,
23+
points: 0,
24+
highscore: 0,
25+
secondsRemaining: null,
26+
};
27+
28+
const reducer = (state, action) => {
29+
switch (action.type) {
30+
case "dataReceived":
31+
return { ...state, questions: action.payload, status: "ready" };
32+
case "dataFailed":
33+
return { ...state, status: "error" };
34+
case "start":
35+
return {
36+
...state,
37+
status: "active",
38+
secondsRemaining: state.questions.length * SECS_PER_QUESTION,
39+
};
40+
case "newAnswer": {
41+
const question = state.questions.at(state.index);
42+
43+
return {
44+
...state,
45+
answer: action.payload,
46+
points:
47+
action.payload === question.correctOption
48+
? state.points + question.points
49+
: state.points,
50+
};
51+
}
52+
case "nextQuestion":
53+
return {
54+
...state,
55+
index: state.index + 1,
56+
answer: null,
57+
};
58+
case "finish":
59+
return {
60+
...state,
61+
status: "finished",
62+
highscore:
63+
state.points > state.highscore ? state.points : state.highscore,
64+
};
65+
case "restart":
66+
return {
67+
...initialState,
68+
questions: state.questions,
69+
status: "ready",
70+
highscore: state.highscore,
71+
};
72+
case "tick":
73+
return {
74+
...state,
75+
secondsRemaining: state.secondsRemaining - 1,
76+
status: state.secondsRemaining === 0 ? "finished" : state.status,
77+
};
78+
79+
default:
80+
throw new Error("Action unknown");
81+
}
82+
};
83+
84+
const App = () => {
85+
const [
86+
{ questions, status, index, answer, points, highscore, secondsRemaining },
87+
dispatch,
88+
] = useReducer(reducer, initialState);
89+
90+
const numQuestions = questions.length;
91+
const maxPossiblePoints = questions.reduce(
92+
(prev, curr) => prev + curr.points,
93+
0
94+
);
95+
96+
useEffect(() => {
97+
fetch("http://localhost:9000/questions")
98+
.then((res) => res.json())
99+
.then((data) => dispatch({ type: "dataReceived", payload: data }))
100+
.catch(() => dispatch({ type: "dataFailed" }));
101+
}, []);
102+
103+
return (
104+
<div className="app">
105+
<Header />
106+
107+
<Main>
108+
{status === "loading" && <Loader />}
109+
{status === "error" && <Error />}
110+
{status === "ready" && (
111+
<StartScreen numQuestions={numQuestions} dispatch={dispatch} />
112+
)}
113+
{status === "active" && (
114+
<>
115+
<Progress
116+
index={index}
117+
numQuestions={numQuestions}
118+
points={points}
119+
maxPossiblePoints={maxPossiblePoints}
120+
answer={answer}
121+
/>
122+
<Questions
123+
question={questions[index]}
124+
answer={answer}
125+
dispatch={dispatch}
126+
/>
127+
<Footer>
128+
<Timer secondsRemaining={secondsRemaining} dispatch={dispatch} />
129+
<NextButton
130+
dispatch={dispatch}
131+
answer={answer}
132+
numQuestions={numQuestions}
133+
index={index}
134+
/>
135+
</Footer>
136+
</>
137+
)}
138+
{status === "finished" && (
139+
<FinishScreen
140+
points={points}
141+
maxPossiblePoints={maxPossiblePoints}
142+
highscore={highscore}
143+
dispatch={dispatch}
144+
/>
145+
)}
146+
</Main>
147+
</div>
148+
);
149+
};
150+
151+
export default App;
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { useReducer } from "react";
2+
3+
const initialState = { count: 0, step: 1 };
4+
5+
const reducer = (state, action) => {
6+
console.log(state, action);
7+
8+
switch (action.type) {
9+
case "dec":
10+
return { ...state, count: state.count - state.step };
11+
case "inc":
12+
return { ...state, count: state.count + state.step };
13+
case "setCount":
14+
return { ...state, count: action.payload };
15+
case "reset":
16+
return initialState;
17+
case "setStep":
18+
return { ...state, step: action.payload };
19+
default:
20+
throw new Error("Unknown action");
21+
}
22+
23+
// if (action.type === "inc") return state + 1;
24+
// if (action.type === "dec") return state - 1;
25+
// if (action.type === "setCount") return action.payload;
26+
};
27+
28+
function DateCounter() {
29+
// const [count, setCount] = useState(0);
30+
// const [step, setStep] = useState(1);
31+
32+
const [state, dispatch] = useReducer(reducer, initialState);
33+
const { count, step } = state;
34+
35+
// This mutates the date object.
36+
const date = new Date("june 21 2027");
37+
date.setDate(date.getDate() + count);
38+
39+
const dec = function () {
40+
dispatch({ type: "dec" });
41+
42+
// setCount((count) => count - 1);
43+
// setCount((count) => count - step);
44+
};
45+
46+
const inc = function () {
47+
dispatch({ type: "inc" });
48+
49+
// setCount((count) => count + 1);
50+
// setCount((count) => count + step);
51+
};
52+
53+
const defineCount = function (e) {
54+
dispatch({ type: "setCount", payload: Number(e.target.value) });
55+
56+
// setCount(Number(e.target.value));
57+
};
58+
59+
const defineStep = function (e) {
60+
dispatch({ type: "setStep", payload: Number(e.target.value) });
61+
62+
// setStep(Number(e.target.value));
63+
};
64+
65+
const reset = function () {
66+
dispatch({ type: "reset" });
67+
68+
// setCount(0);
69+
// setStep(1);
70+
};
71+
72+
return (
73+
<div className="counter">
74+
<div>
75+
<input
76+
type="range"
77+
min="0"
78+
max="10"
79+
value={step}
80+
onChange={defineStep}
81+
/>
82+
<span>{step}</span>
83+
</div>
84+
85+
<div>
86+
<button onClick={dec}>-</button>
87+
<input value={count} onChange={defineCount} />
88+
<button onClick={inc}>+</button>
89+
</div>
90+
91+
<p>{date.toDateString()}</p>
92+
93+
<div>
94+
<button onClick={reset}>Reset</button>
95+
</div>
96+
</div>
97+
);
98+
}
99+
export default DateCounter;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function Error() {
2+
return (
3+
<p className="error">
4+
<span>💥</span> There was an error fecthing questions.
5+
</p>
6+
);
7+
}
8+
9+
export default Error;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function Header() {
2+
return (
3+
<header className='app-header'>
4+
<img src='logo512.png' alt='React logo' />
5+
<h1>The React Quiz</h1>
6+
</header>
7+
);
8+
}
9+
10+
export default Header;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function Loader() {
2+
return (
3+
<div className="loader-container">
4+
<div className="loader"></div>
5+
<p>Loading questions...</p>
6+
</div>
7+
);
8+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const FinishScreen = ({ points, maxPossiblePoints, highscore, dispatch }) => {
2+
const percentage = Math.ceil((points / maxPossiblePoints) * 100);
3+
4+
let emoji;
5+
if (percentage === 100) emoji = "🥇";
6+
if (percentage >= 80 && percentage < 100) emoji = "🎉";
7+
if (percentage >= 50 && percentage < 80) emoji = "🙃";
8+
if (percentage > 0 && percentage < 50) emoji = "🤨";
9+
if (percentage === 0) emoji = "🤦‍♂️";
10+
11+
return (
12+
<>
13+
<p className="result">
14+
<span>{emoji}</span> You scored <strong>{points}</strong> out of{" "}
15+
{maxPossiblePoints} ({percentage}%)
16+
</p>
17+
<p className="highscore">(Highscore: {highscore} points)</p>
18+
<button
19+
className="btn btn-ui"
20+
onClick={() => dispatch({ type: "restart" })}
21+
>
22+
Restart quiz
23+
</button>
24+
</>
25+
);
26+
};
27+
28+
export default FinishScreen;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const Footer = ({ children }) => {
2+
return <footer>{children}</footer>;
3+
};
4+
5+
export default Footer;

0 commit comments

Comments
 (0)