forked from jonasschmedtmann/ultimate-react-course
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Completed exercise jonasschmedtmann#13: Workout Timer in section 19 o…
…f the course
- Loading branch information
Showing
90 changed files
with
18,661 additions
and
34,629 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
10-react-quiz/starter/src (2)/components/Header/Header.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
28
10-react-quiz/starter/src (2)/components/Main/FinishScreen.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
const Footer = ({ children }) => { | ||
return <footer>{children}</footer>; | ||
}; | ||
|
||
export default Footer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
25
10-react-quiz/starter/src (2)/components/Main/NextButton.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.