├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── Loading.js
├── Modal.js
├── SetupForm.js
├── context.js
├── index.css
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-projects-23-quiz/a0056f79079d66a3032a080c9d635f215e882406/README.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reminder",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.5.0",
8 | "@testing-library/user-event": "^7.2.1",
9 | "axios": "^0.21.0",
10 | "react": "^16.13.1",
11 | "react-dom": "^16.13.1",
12 | "react-icons": "^3.11.0",
13 | "react-scripts": "3.4.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "CI= react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": {
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ],
30 | "development": [
31 | "last 1 chrome version",
32 | "last 1 firefox version",
33 | "last 1 safari version"
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-projects-23-quiz/a0056f79079d66a3032a080c9d635f215e882406/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Quiz Complete
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-projects-23-quiz/a0056f79079d66a3032a080c9d635f215e882406/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-projects-23-quiz/a0056f79079d66a3032a080c9d635f215e882406/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useGlobalContext } from './context'
3 |
4 | import SetupForm from './SetupForm'
5 | import Loading from './Loading'
6 | import Modal from './Modal'
7 | function App() {
8 | const {
9 | waiting,
10 | loading,
11 | questions,
12 | index,
13 | correct,
14 | nextQuestion,
15 | checkAnswer,
16 | } = useGlobalContext()
17 | if (waiting) {
18 | return
19 | }
20 | if (loading) {
21 | return
22 | }
23 |
24 | const { question, incorrect_answers, correct_answer } = questions[index]
25 | // const answers = [...incorrect_answers, correct_answer]
26 | let answers = [...incorrect_answers]
27 | const tempIndex = Math.floor(Math.random() * 4)
28 | if (tempIndex === 3) {
29 | answers.push(correct_answer)
30 | } else {
31 | answers.push(answers[tempIndex])
32 | answers[tempIndex] = correct_answer
33 | }
34 | return (
35 |
36 |
37 |
38 |
39 | correct answers : {correct}/{index}
40 |
41 |
42 |
43 |
44 | {answers.map((answer, index) => {
45 | return (
46 |
55 |
56 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default App
65 |
--------------------------------------------------------------------------------
/src/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Loading = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default Loading
12 |
--------------------------------------------------------------------------------
/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useGlobalContext } from './context'
3 |
4 | const Modal = () => {
5 | const { isModalOpen, closeModal, correct, questions } = useGlobalContext()
6 | return (
7 |
12 |
13 |
congrats!
14 |
15 | You answered {((correct / questions.length) * 100).toFixed(0)}% of
16 | questions correctly
17 |
18 |
21 |
22 |
23 | )
24 | }
25 |
26 | export default Modal
27 |
--------------------------------------------------------------------------------
/src/SetupForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useGlobalContext } from './context'
3 |
4 | const SetupForm = () => {
5 | const { quiz, handleChange, handleSubmit, error } = useGlobalContext()
6 | return (
7 |
8 |
67 |
68 | )
69 | }
70 |
71 | export default SetupForm
72 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import React, { useState, useContext, useEffect } from 'react'
3 |
4 | const table = {
5 | sports: 21,
6 | history: 23,
7 | politics: 24,
8 | }
9 |
10 | const API_ENDPOINT = 'https://opentdb.com/api.php?'
11 |
12 | const url = ''
13 | const tempUrl =
14 | 'https://opentdb.com/api.php?amount=10&category=21&difficulty=easy&type=multiple'
15 | const AppContext = React.createContext()
16 |
17 | const AppProvider = ({ children }) => {
18 | const [waiting, setWaiting] = useState(true)
19 | const [loading, setLoading] = useState(false)
20 | const [questions, setQuestions] = useState([])
21 | const [index, setIndex] = useState(0)
22 | const [correct, setCorrect] = useState(0)
23 | const [error, setError] = useState(false)
24 | const [quiz, setQuiz] = useState({
25 | amount: 10,
26 | category: 'sports',
27 | difficulty: 'easy',
28 | })
29 | const [isModalOpen, setIsModalOpen] = useState(false)
30 |
31 | const fetchQuestions = async (url) => {
32 | setLoading(true)
33 | setWaiting(false)
34 | const response = await axios(url).catch((err) => console.log(err))
35 | if (response) {
36 | const data = response.data.results
37 | if (data.length > 0) {
38 | setQuestions(data)
39 | setLoading(false)
40 | setWaiting(false)
41 | setError(false)
42 | } else {
43 | setWaiting(true)
44 | setError(true)
45 | }
46 | } else {
47 | setWaiting(true)
48 | }
49 | }
50 |
51 | const nextQuestion = () => {
52 | setIndex((oldIndex) => {
53 | const index = oldIndex + 1
54 | if (index > questions.length - 1) {
55 | openModal()
56 | return 0
57 | } else {
58 | return index
59 | }
60 | })
61 | }
62 | const checkAnswer = (value) => {
63 | if (value) {
64 | setCorrect((oldState) => oldState + 1)
65 | }
66 | nextQuestion()
67 | }
68 |
69 | const openModal = () => {
70 | setIsModalOpen(true)
71 | }
72 | const closeModal = () => {
73 | setWaiting(true)
74 | setCorrect(0)
75 | setIsModalOpen(false)
76 | }
77 | const handleChange = (e) => {
78 | const name = e.target.name
79 | const value = e.target.value
80 | setQuiz({ ...quiz, [name]: value })
81 | }
82 | const handleSubmit = (e) => {
83 | e.preventDefault()
84 | const { amount, category, difficulty } = quiz
85 |
86 | const url = `${API_ENDPOINT}amount=${amount}&difficulty=${difficulty}&category=${table[category]}&type=multiple`
87 | fetchQuestions(url)
88 | }
89 |
90 | return (
91 |
108 | {children}
109 |
110 | )
111 | }
112 | // make sure use
113 | export const useGlobalContext = () => {
114 | return useContext(AppContext)
115 | }
116 |
117 | export { AppContext, AppProvider }
118 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /*
2 | ===============
3 | Variables
4 | ===============
5 | */
6 |
7 | :root {
8 | /* dark shades of primary color*/
9 | --clr-primary-1: hsl(205, 86%, 17%);
10 | --clr-primary-2: hsl(205, 77%, 27%);
11 | --clr-primary-3: hsl(205, 72%, 37%);
12 | --clr-primary-4: hsl(205, 63%, 48%);
13 | /* primary/main color */
14 | --clr-primary-5: hsl(205, 78%, 60%);
15 | /* lighter shades of primary color */
16 | --clr-primary-6: hsl(205, 89%, 70%);
17 | --clr-primary-7: hsl(205, 90%, 76%);
18 | --clr-primary-8: hsl(205, 86%, 81%);
19 | --clr-primary-9: hsl(205, 90%, 88%);
20 | --clr-primary-10: hsl(205, 100%, 96%);
21 | /* darkest grey - used for headings */
22 | --clr-grey-1: hsl(209, 61%, 16%);
23 | --clr-grey-2: hsl(211, 39%, 23%);
24 | --clr-grey-3: hsl(209, 34%, 30%);
25 | --clr-grey-4: hsl(209, 28%, 39%);
26 | /* grey used for paragraphs */
27 | --clr-grey-5: hsl(210, 22%, 49%);
28 | --clr-grey-6: hsl(209, 23%, 60%);
29 | --clr-grey-7: hsl(211, 27%, 70%);
30 | --clr-grey-8: hsl(210, 31%, 80%);
31 | --clr-grey-9: hsl(212, 33%, 89%);
32 | --clr-grey-10: hsl(210, 36%, 96%);
33 | --clr-white: #fff;
34 | --clr-red-dark: hsl(360, 67%, 44%);
35 | --clr-red-light: hsl(360, 71%, 66%);
36 | --clr-green-dark: hsl(125, 67%, 44%);
37 | --clr-green-light: hsl(125, 71%, 66%);
38 | --clr-black: #222;
39 | --transition: all 0.3s linear;
40 | --spacing: 0.1rem;
41 | --radius: 0.25rem;
42 | --light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
43 | --dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
44 | --max-width: 1170px;
45 | --fixed-width: 620px;
46 | }
47 | /*
48 | ===============
49 | Global Styles
50 | ===============
51 | */
52 |
53 | *,
54 | ::after,
55 | ::before {
56 | margin: 0;
57 | padding: 0;
58 | box-sizing: border-box;
59 | }
60 | body {
61 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
62 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
63 | background: var(--clr-grey-10);
64 | color: var(--clr-grey-1);
65 | line-height: 1.5;
66 | font-size: 0.875rem;
67 | }
68 | ul {
69 | list-style-type: none;
70 | }
71 | a {
72 | text-decoration: none;
73 | }
74 | h1,
75 | h2,
76 | h3,
77 | h4 {
78 | letter-spacing: var(--spacing);
79 | text-transform: capitalize;
80 | line-height: 1.25;
81 | margin-bottom: 0.75rem;
82 | }
83 | h1 {
84 | font-size: 2.5rem;
85 | }
86 | h2 {
87 | font-size: 2rem;
88 | }
89 | h3 {
90 | font-size: 1.25rem;
91 | }
92 | h4 {
93 | font-size: 0.875rem;
94 | }
95 | p {
96 | margin-bottom: 1.25rem;
97 | color: var(--clr-grey-3);
98 | }
99 | @media screen and (min-width: 800px) {
100 | h1 {
101 | font-size: 3rem;
102 | }
103 | h2 {
104 | font-size: 2.5rem;
105 | }
106 | h3 {
107 | font-size: 1.75rem;
108 | }
109 | h4 {
110 | font-size: 1rem;
111 | }
112 | body {
113 | font-size: 1rem;
114 | }
115 | h1,
116 | h2,
117 | h3,
118 | h4 {
119 | line-height: 1;
120 | }
121 | }
122 | /* global classes */
123 |
124 | /* section */
125 | .section {
126 | width: 90vw;
127 | margin: 0 auto;
128 | max-width: var(--max-width);
129 | }
130 |
131 | @media screen and (min-width: 992px) {
132 | .section {
133 | width: 95vw;
134 | }
135 | }
136 |
137 | /*
138 | ===============
139 | Quiz
140 | ===============
141 | */
142 |
143 | main {
144 | min-height: 100vh;
145 | display: flex;
146 | justify-content: center;
147 | align-items: center;
148 | }
149 | .quiz {
150 | width: 90vw;
151 | max-width: var(--max-width);
152 | margin: 4rem auto;
153 | background: var(--clr-white);
154 | border-radius: var(--radius);
155 | padding: 3rem;
156 | }
157 | .quiz-small {
158 | max-width: 500px;
159 | }
160 | .container h2 {
161 | margin-bottom: 2rem;
162 | text-align: center;
163 | line-height: 1.5;
164 | text-transform: none;
165 | }
166 | .answer-btn {
167 | display: block;
168 | width: 100%;
169 | margin: 0.75rem auto;
170 | background: var(--clr-primary-7);
171 | border-radius: var(--radius);
172 | border-color: transparent;
173 | color: var(--clr-black);
174 | letter-spacing: var(--spacing);
175 | font-size: 1rem;
176 | cursor: pointer;
177 | padding: 0.5rem 0;
178 | transition: var(--transition);
179 | }
180 | @media screen and (min-width: 576px) {
181 | .answer-btn {
182 | max-width: 60%;
183 | }
184 | }
185 | .answer-btn:hover {
186 | background: var(--clr-primary-5);
187 | color: var(--clr-white);
188 | }
189 | .correct-answers {
190 | font-size: 1rem;
191 | margin-bottom: 2rem;
192 | text-align: right;
193 | text-transform: capitalize;
194 | letter-spacing: var(--spacing);
195 | color: var(--clr-green-dark);
196 | }
197 | .next-question,
198 | .close-btn,
199 | .submit-btn {
200 | border-radius: var(--radius);
201 | border-color: transparent;
202 | padding: 0.25rem 0.5rem;
203 | display: block;
204 | width: 35%;
205 | margin-left: auto;
206 | margin-top: 2rem;
207 | text-transform: capitalize;
208 | font-weight: 700;
209 | letter-spacing: var(--spacing);
210 | font-size: 1rem;
211 | background: #ffb100;
212 | color: var(--clr-black);
213 | transition: var(--transition);
214 | cursor: pointer;
215 | }
216 | .next-question:hover {
217 | background: #805900;
218 | color: #ffb100;
219 | }
220 | /*
221 | ===============
222 | Form
223 | ===============
224 | */
225 |
226 | .setup-form h2 {
227 | margin-bottom: 2rem;
228 | }
229 | .form-control {
230 | margin-bottom: 2rem;
231 | }
232 | .form-control label {
233 | display: block;
234 | text-transform: capitalize;
235 | font-weight: 500;
236 | color: var(--clr-grey-3);
237 | margin-bottom: 0.5rem;
238 | }
239 | .form-input {
240 | border: none;
241 | background: var(--clr-grey-10);
242 | font-size: 1rem;
243 | padding: 0.25rem 0.5rem;
244 | width: 100%;
245 | border-radius: var(--radius);
246 | }
247 | .error {
248 | color: var(--clr-red-dark);
249 | text-transform: capitalize;
250 | }
251 | .submit-btn {
252 | width: 100%;
253 | margin-top: 3rem;
254 | }
255 | /*
256 | ===============
257 | Loading
258 | ===============
259 | */
260 | @keyframes spinner {
261 | to {
262 | transform: rotate(360deg);
263 | }
264 | }
265 |
266 | .loading {
267 | width: 6rem;
268 | height: 6rem;
269 | margin: 0 auto;
270 | margin-top: 10rem;
271 | border-radius: 50%;
272 | border: 3px solid #ccc;
273 | border-top-color: var(--clr-primary-5);
274 | animation: spinner 0.6s linear infinite;
275 | }
276 |
277 | .modal-container {
278 | position: fixed;
279 | top: 0;
280 | left: 0;
281 | width: 100%;
282 | height: 100%;
283 | background: rgba(0, 0, 0, 0.75);
284 | display: flex;
285 | align-items: center;
286 | justify-content: center;
287 | opacity: 0;
288 | transition: var(--transition);
289 | z-index: -1;
290 | }
291 | .isOpen {
292 | opacity: 1;
293 | z-index: 999;
294 | }
295 |
296 | .modal-content {
297 | background: var(--clr-white);
298 | width: 90vw;
299 | max-width: var(--fixed-width);
300 | padding: 3rem;
301 | border-radius: var(--radius);
302 | text-align: center;
303 | position: relative;
304 | }
305 | .modal-content p {
306 | font-size: 1.5rem;
307 | text-transform: none;
308 | }
309 | .close-btn {
310 | margin-right: auto;
311 | }
312 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 | import { AppProvider } from './context'
6 |
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | )
15 |
--------------------------------------------------------------------------------