Skip to content

Commit

Permalink
Completed exercise jonasschmedtmann#13: Workout Timer in section 19 o…
Browse files Browse the repository at this point in the history
…f the course
  • Loading branch information
MI-Programmer committed Jun 16, 2024
2 parents 9ceb853 + 7b4d12a commit a9672bb
Show file tree
Hide file tree
Showing 90 changed files with 18,661 additions and 34,629 deletions.
1,748 changes: 1,272 additions & 476 deletions 10-react-quiz/starter/package-lock.json

Large diffs are not rendered by default.

Empty file.
151 changes: 151 additions & 0 deletions 10-react-quiz/starter/src (2)/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { useEffect, useReducer } from "react";
import Header from "./components/Header/Header";
import Main from "./components/Main/Main";
import Loader from "./components/Loader";
import Error from "./components/Error";
import StartScreen from "./components/Main/StartScreen";
import Questions from "./components/Main/Questions";
import NextButton from "./components/Main/NextButton";
import Progress from "./components/Main/Progress";
import FinishScreen from "./components/Main/FinishScreen";
import Footer from "./components/Main/Footer";
import Timer from "./components/Main/Timer";

const SECS_PER_QUESTION = 30;

const initialState = {
questions: [],

// "loading", "error", "ready", "active", "finished"
status: "loading",
index: 0,
answer: null,
points: 0,
highscore: 0,
secondsRemaining: null,
};

const reducer = (state, action) => {
switch (action.type) {
case "dataReceived":
return { ...state, questions: action.payload, status: "ready" };
case "dataFailed":
return { ...state, status: "error" };
case "start":
return {
...state,
status: "active",
secondsRemaining: state.questions.length * SECS_PER_QUESTION,
};
case "newAnswer": {
const question = state.questions.at(state.index);

return {
...state,
answer: action.payload,
points:
action.payload === question.correctOption
? state.points + question.points
: state.points,
};
}
case "nextQuestion":
return {
...state,
index: state.index + 1,
answer: null,
};
case "finish":
return {
...state,
status: "finished",
highscore:
state.points > state.highscore ? state.points : state.highscore,
};
case "restart":
return {
...initialState,
questions: state.questions,
status: "ready",
highscore: state.highscore,
};
case "tick":
return {
...state,
secondsRemaining: state.secondsRemaining - 1,
status: state.secondsRemaining === 0 ? "finished" : state.status,
};

default:
throw new Error("Action unknown");
}
};

const App = () => {
const [
{ questions, status, index, answer, points, highscore, secondsRemaining },
dispatch,
] = useReducer(reducer, initialState);

const numQuestions = questions.length;
const maxPossiblePoints = questions.reduce(
(prev, curr) => prev + curr.points,
0
);

useEffect(() => {
fetch("http://localhost:9000/questions")
.then((res) => res.json())
.then((data) => dispatch({ type: "dataReceived", payload: data }))
.catch(() => dispatch({ type: "dataFailed" }));
}, []);

return (
<div className="app">
<Header />

<Main>
{status === "loading" && <Loader />}
{status === "error" && <Error />}
{status === "ready" && (
<StartScreen numQuestions={numQuestions} dispatch={dispatch} />
)}
{status === "active" && (
<>
<Progress
index={index}
numQuestions={numQuestions}
points={points}
maxPossiblePoints={maxPossiblePoints}
answer={answer}
/>
<Questions
question={questions[index]}
answer={answer}
dispatch={dispatch}
/>
<Footer>
<Timer secondsRemaining={secondsRemaining} dispatch={dispatch} />
<NextButton
dispatch={dispatch}
answer={answer}
numQuestions={numQuestions}
index={index}
/>
</Footer>
</>
)}
{status === "finished" && (
<FinishScreen
points={points}
maxPossiblePoints={maxPossiblePoints}
highscore={highscore}
dispatch={dispatch}
/>
)}
</Main>
</div>
);
};

export default App;
1 change: 1 addition & 0 deletions 10-react-quiz/starter/src (2)/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions 10-react-quiz/starter/src (2)/components/DateCounter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useReducer } from "react";

const initialState = { count: 0, step: 1 };

const reducer = (state, action) => {
console.log(state, action);

switch (action.type) {
case "dec":
return { ...state, count: state.count - state.step };
case "inc":
return { ...state, count: state.count + state.step };
case "setCount":
return { ...state, count: action.payload };
case "reset":
return initialState;
case "setStep":
return { ...state, step: action.payload };
default:
throw new Error("Unknown action");
}

// if (action.type === "inc") return state + 1;
// if (action.type === "dec") return state - 1;
// if (action.type === "setCount") return action.payload;
};

function DateCounter() {
// const [count, setCount] = useState(0);
// const [step, setStep] = useState(1);

const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

// This mutates the date object.
const date = new Date("june 21 2027");
date.setDate(date.getDate() + count);

const dec = function () {
dispatch({ type: "dec" });

// setCount((count) => count - 1);
// setCount((count) => count - step);
};

const inc = function () {
dispatch({ type: "inc" });

// setCount((count) => count + 1);
// setCount((count) => count + step);
};

const defineCount = function (e) {
dispatch({ type: "setCount", payload: Number(e.target.value) });

// setCount(Number(e.target.value));
};

const defineStep = function (e) {
dispatch({ type: "setStep", payload: Number(e.target.value) });

// setStep(Number(e.target.value));
};

const reset = function () {
dispatch({ type: "reset" });

// setCount(0);
// setStep(1);
};

return (
<div className="counter">
<div>
<input
type="range"
min="0"
max="10"
value={step}
onChange={defineStep}
/>
<span>{step}</span>
</div>

<div>
<button onClick={dec}>-</button>
<input value={count} onChange={defineCount} />
<button onClick={inc}>+</button>
</div>

<p>{date.toDateString()}</p>

<div>
<button onClick={reset}>Reset</button>
</div>
</div>
);
}
export default DateCounter;
9 changes: 9 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Error.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function Error() {
return (
<p className="error">
<span>💥</span> There was an error fecthing questions.
</p>
);
}

export default Error;
10 changes: 10 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function Header() {
return (
<header className='app-header'>
<img src='logo512.png' alt='React logo' />
<h1>The React Quiz</h1>
</header>
);
}

export default Header;
8 changes: 8 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Loader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function Loader() {
return (
<div className="loader-container">
<div className="loader"></div>
<p>Loading questions...</p>
</div>
);
}
28 changes: 28 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Main/FinishScreen.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const FinishScreen = ({ points, maxPossiblePoints, highscore, dispatch }) => {
const percentage = Math.ceil((points / maxPossiblePoints) * 100);

let emoji;
if (percentage === 100) emoji = "🥇";
if (percentage >= 80 && percentage < 100) emoji = "🎉";
if (percentage >= 50 && percentage < 80) emoji = "🙃";
if (percentage > 0 && percentage < 50) emoji = "🤨";
if (percentage === 0) emoji = "🤦‍♂️";

return (
<>
<p className="result">
<span>{emoji}</span> You scored <strong>{points}</strong> out of{" "}
{maxPossiblePoints} ({percentage}%)
</p>
<p className="highscore">(Highscore: {highscore} points)</p>
<button
className="btn btn-ui"
onClick={() => dispatch({ type: "restart" })}
>
Restart quiz
</button>
</>
);
};

export default FinishScreen;
5 changes: 5 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Main/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Footer = ({ children }) => {
return <footer>{children}</footer>;
};

export default Footer;
5 changes: 5 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Main/Main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Main = ({ children }) => {
return <main className="main">{children}</main>;
};

export default Main;
25 changes: 25 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Main/NextButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const NextButton = ({ dispatch, answer, numQuestions, index }) => {
if (answer === null) return null;

if (index < numQuestions - 1)
return (
<button
className="btn btn-ui"
onClick={() => dispatch({ type: "nextQuestion" })}
>
Next
</button>
);

if (index === numQuestions - 1)
return (
<button
className="btn btn-ui"
onClick={() => dispatch({ type: "finish" })}
>
Finish
</button>
);
};

export default NextButton;
26 changes: 26 additions & 0 deletions 10-react-quiz/starter/src (2)/components/Main/Options.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const Options = ({ question, answer, dispatch }) => {
const hasAnswered = answer !== null;

return (
<div className="options">
{question.options.map((option, index) => (
<button
key={option}
className={`btn btn-option ${index === answer ? "answer" : ""} ${
hasAnswered
? index === question.correctOption
? "correct"
: "wrong"
: ""
}`}
onClick={() => dispatch({ type: "newAnswer", payload: index })}
disabled={hasAnswered}
>
{option}
</button>
))}
</div>
);
};

export default Options;
Loading

0 comments on commit a9672bb

Please sign in to comment.