├── README.md
├── interfaces.ts
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── sample-data.json
├── server.ts
├── src
├── App.tsx
├── components
│ ├── AnswerBlock.tsx
│ ├── QuestionBlock.tsx
│ ├── QuestionsBlock.tsx
│ └── Title.tsx
├── index.css
└── index.tsx
└── tsconfig.json
/README.md:
--------------------------------------------------------------------------------
1 | # Build a Buzzfeed Clone in TypeScript + REST API Database + Node.js!
2 |
3 | Watch the full tutorial here: Build a Buzzfeed Clone in TypeScript + REST API Database + Node.js!
4 |
--------------------------------------------------------------------------------
/interfaces.ts:
--------------------------------------------------------------------------------
1 | interface QuizData {
2 | title: string;
3 | subtitle: string;
4 | quizId: string;
5 | content: Content[];
6 | answers: Answer[];
7 | }
8 |
9 | interface Answer {
10 | text: string;
11 | image: string;
12 | alt: string;
13 | combination: string[]
14 | }
15 |
16 | interface Content {
17 | id: number;
18 | text: string;
19 | questions: Question[];
20 | }
21 |
22 | interface Question {
23 | text: string;
24 | image: string;
25 | alt: string;
26 | credit: string;
27 | }
28 |
29 | export type { QuizData, Answer, Content, Question}
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-buzzfeed-clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@types/express": "^4.17.13",
7 | "@types/node": "^16.11.58",
8 | "@types/react": "^18.0.19",
9 | "@types/react-dom": "^18.0.6",
10 | "axios": "^0.27.2",
11 | "dotenv": "^16.0.2",
12 | "nodemon": "^2.0.19",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-scripts": "5.0.1",
16 | "ts-node": "^10.9.1",
17 | "typescript": "^4.8.3",
18 | "web-vitals": "^2.1.4"
19 | },
20 | "scripts": {
21 | "start:frontend": "react-scripts start",
22 | "start:backend": "nodemon server.ts",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kubowania/buzzfeed-clone-typescript-database/d802c44f96117a2e0cca2c69702acb5eccabccf4/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kubowania/buzzfeed-clone-typescript-database/d802c44f96117a2e0cca2c69702acb5eccabccf4/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kubowania/buzzfeed-clone-typescript-database/d802c44f96117a2e0cca2c69702acb5eccabccf4/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 |
--------------------------------------------------------------------------------
/sample-data.json:
--------------------------------------------------------------------------------
1 | {
2 | "quizId": "3483j33",
3 | "title": "What cheese are you?",
4 | "subtitle": "This quiz isn't cheesy or anything like that...",
5 | "content": [
6 | {
7 | "id": 0,
8 | "text": "Pick a vacation destination:",
9 | "questions": [
10 | {
11 | "text": "New York",
12 | "image": "https://images.unsplash.com/photo-1534430480872-3498386e7856?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
13 | "alt": "Photo of Empire State Building during daytime",
14 | "credit": "Oliver Niblett"
15 | },
16 | {
17 | "text": "Austin",
18 | "image": "https://images.unsplash.com/photo-1531218150217-54595bc2b934?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
19 | "alt": "Time-lapse photography car lights on bridge",
20 | "credit": "Carlos Alfonso"
21 | },
22 | {
23 | "text": "Portland",
24 | "image": "https://images.unsplash.com/photo-1534430480872-3498386e7856?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
25 | "alt": "High-rise buildings",
26 | "credit": "Elena Kuchko"
27 | },
28 | {
29 | "text": "New Orleans",
30 | "image": "https://images.unsplash.com/photo-1549965738-e1aaf1168943?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
31 | "alt": "Road with people and house",
32 | "credit": "João Francisco"
33 | }
34 | ]
35 | },
36 | {
37 | "id": 1,
38 | "text": "Pick some food:",
39 | "questions": [
40 | {
41 | "text": "Pizza",
42 | "image": "https://images.unsplash.com/photo-1534308983496-4fabb1a015ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
43 | "alt": "Pepperoni Pizza",
44 | "credit": "Alan Hardman"
45 | },
46 | {
47 | "text": "Sandwich",
48 | "image": "https://images.unsplash.com/photo-1481070414801-51fd732d7184?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
49 | "alt": "ham sandwich on white surface",
50 | "credit": "Eaters Collective"
51 | },
52 | {
53 | "text": "Pasta",
54 | "image": "https://images.unsplash.com/photo-1516100882582-96c3a05fe590?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
55 | "alt": "Pasta in tomato sauce",
56 | "credit": "Mgg Vitchakorn"
57 | },
58 | {
59 | "text": "Hamburger",
60 | "image": "https://images.unsplash.com/photo-1551782450-a2132b4ba21d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
61 | "alt": "hamburger",
62 | "credit": "sk"
63 | }
64 | ]
65 | },
66 | {
67 | "id": 2,
68 | "text": "Pick a home:",
69 | "questions": [
70 | {
71 | "text": "Traditional",
72 | "image": "https://images.unsplash.com/photo-1555040479-c949debe66c1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
73 | "alt": "focus photography of building windows",
74 | "credit": "Burgess Milner"
75 | },
76 | {
77 | "text": "Modern",
78 | "image": "https://images.unsplash.com/photo-1460317442991-0ec209397118?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
79 | "alt": "low angle view of building",
80 | "credit": "Brandon Giggs"
81 | },
82 | {
83 | "text": "House",
84 | "image": "https://images.unsplash.com/photo-1572120360610-d971b9d7767c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
85 | "alt": "trees beside white house",
86 | "credit": "Phil Hearing"
87 | },
88 | {
89 | "text": "Mountains",
90 | "image": "https://images.unsplash.com/photo-1506974210756-8e1b8985d348?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&fit=crop&h=230&w=320&crop=edges",
91 | "alt": "brown wooden cabin infront of forest",
92 | "credit": "eulauretta"
93 | }
94 | ]
95 | }
96 | ],
97 | "answers": [
98 | {
99 | "combination": ["New York", "Pizza", "Traditional"],
100 | "text": "Blue Cheese",
101 | "image": "https://images.unsplash.com/photo-1452195100486-9cc805987862?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjczMTc0fQ&w=400&h=400&fit=fillmax",
102 | "alt": "blue cheese"
103 | },
104 | {
105 | "combination": ["Austin", "Pasta", "Modern"],
106 | "text": "Cheddar",
107 | "image": "https://images.unsplash.com/photo-1618164436241-4473940d1f5c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80",
108 | "alt": "cheddar cheese"
109 | },
110 | {
111 | "combination": ["Portland", "Sandwich", "Mountains"],
112 | "text": "Feta",
113 | "image": "https://images.unsplash.com/photo-1626957341926-98752fc2ba90?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
114 | "alt": "feta"
115 | },
116 | {
117 | "combination": ["New Orleans", "Hamburger", "House"],
118 | "text": "Halloumi",
119 | "image": "https://images.unsplash.com/photo-1598167912234-02576c0c5f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80",
120 | "alt": "halloumi"
121 | },
122 | {
123 | "combination": ["New York", "Pizza", "Modern"],
124 | "text": "Goya",
125 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Queso_Goya.jpg",
126 | "alt": "Goya"
127 | },
128 | {
129 | "combination": ["New York", "Pizza", "Mountains"],
130 | "text": "Red Hawk",
131 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Cowgirl_Creamery_Point_Reyes_-_Red_Hawk_cheese.jpg",
132 | "alt": "Red Hawk"
133 | },
134 | {
135 | "combination": ["New York", "Pizza", "House"],
136 | "text": "Pepper Jack",
137 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Pepperjack_Cheese.jpg",
138 | "alt": "Pepper Jack"
139 | },
140 | {
141 | "combination": ["New York", "Pasta", "Traditional"],
142 | "text": "Nacho cheese",
143 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Nachos_supreme.jpg/270px-Nachos_supreme.jpg",
144 | "alt": "Nacho cheese"
145 | },
146 | {
147 | "combination": ["New York", "Pasta", "Modern"],
148 | "text": "Muenster",
149 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Block_of_Muenster_cheese.jpg",
150 | "alt": "Muenster"
151 | },
152 | {
153 | "combination": ["New York", "Pasta", "Mountains"],
154 | "text": "Humboldt Fog",
155 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Cheese_30_bg_051906.jpg",
156 | "alt": "Humboldt Fog"
157 | },
158 | {
159 | "combination": ["New York", "Pasta", "House"],
160 | "text": "Brick",
161 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Brickcheese.jpg",
162 | "alt": "Brick"
163 | },
164 | {
165 | "combination": ["New York", "Sandwich", "Traditional"],
166 | "text": "Oaxaca",
167 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Quesillo_de_Oaxaca.png",
168 | "alt": "Oaxaca"
169 | },
170 | {
171 | "combination": ["New York", "Sandwich", "Modern"],
172 | "text": "Criollo",
173 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Fromage_criollo._%2815228806466%29.jpg",
174 | "alt": "Criollo"
175 | },
176 | {
177 | "combination": ["New York", "Sandwich", "Mountains"],
178 | "text": "Asadero",
179 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Tlayuda_con_Quesillo%2C_Oaxaca.jpg",
180 | "alt": "Asadero"
181 | },
182 | {
183 | "combination": ["New York", "Sandwich", "House"],
184 | "text": "Añejo",
185 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Queso_a%C3%B1ejo.JPG/270px-Queso_a%C3%B1ejo.JPG",
186 | "alt": "Añejo"
187 | },
188 | {
189 | "combination": ["New York", "Hamburger", "Traditional"],
190 | "text": "Adobera",
191 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Adobera.png/270px-Adobera.png",
192 | "alt": "Adobera"
193 | },
194 | {
195 | "combination": ["New York", "Hamburger", "Modern"],
196 | "text": "Quesillo",
197 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Quesillo_de_Oaxaca.png/270px-Quesillo_de_Oaxaca.png",
198 | "alt": "Quesillo"
199 | },
200 | {
201 | "combination": ["New York", "Hamburger", "Mountains"],
202 | "text": "Cuajada",
203 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Cuajada.jpg",
204 | "alt": "Cuajada"
205 | },
206 | {
207 | "combination": ["New York", "Hamburger", "House"],
208 | "text": "Turrialba",
209 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Queso_Turrialba.jpg",
210 | "alt": "Turrialba"
211 | },
212 |
213 | {
214 | "combination": ["Austin", "Pizza", "Traditional"],
215 | "text": "Palmito",
216 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Palmito_cheese.jpg",
217 | "alt": "Palmito"
218 | },
219 | {
220 | "combination": ["Austin", "Pizza", "Modern"],
221 | "text": "Pikauba",
222 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Pikauba_%28fromage%29_03.jpg/270px-Pikauba_%28fromage%29_03.jpg",
223 | "alt": "Pikauba"
224 | },
225 | {
226 | "combination": ["Austin", "Pizza", "Mountains"],
227 | "text": "Hellim",
228 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Halloumislicefresh.jpg/270px-Halloumislicefresh.jpg",
229 | "alt": "Hellim"
230 | },
231 | {
232 | "combination": ["Austin", "Pizza", "House"],
233 | "text": "Beyaz peynir",
234 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/White_cheese_from_Turkey.jpg/270px-White_cheese_from_Turkey.jpg",
235 | "alt": "Beyaz peynir"
236 | },
237 | {
238 | "combination": ["Austin", "Pasta", "Traditional"],
239 | "text": "Nabulsi",
240 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/%D8%AC%D8%A8%D9%86%D8%A9_%D9%86%D8%A7%D8%A8%D9%84%D8%B3%D9%8A%D8%A9.jpg",
241 | "alt": "Nabulsi"
242 | },
243 | {
244 | "combination": ["Austin", "Pasta", "Mountains"],
245 | "text": "Jameed",
246 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Jameed.JPG/270px-Jameed.JPG",
247 | "alt": "Jameed"
248 | },
249 | {
250 | "combination": ["Austin", "Pasta", "House"],
251 | "text": "Labneh",
252 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Labneh01.jpg",
253 | "alt": "Labneh"
254 | },
255 | {
256 | "combination": ["Austin", "Sandwich", "Traditional"],
257 | "text": "Pot Cheese",
258 | "image": "https://upload.wikimedia.org/wikipedia/commons/7/73/Kupe_paniri.jpg",
259 | "alt": "Pot Cheese"
260 | },
261 | {
262 | "combination": ["Austin", "Sandwich", "Modern"],
263 | "text": "Rumi",
264 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Flickr_-_Tour_d%27Afrique_-_ArabicCheese.jpg",
265 | "alt": "Rumi"
266 | },
267 | {
268 | "combination": ["Austin", "Sandwich", "Mountains"],
269 | "text": "Mish",
270 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Mesh.jpg/270px-Mesh.jpg",
271 | "alt": "Mish"
272 | },
273 | {
274 | "combination": ["Austin", "Sandwich", "House"],
275 | "text": "Domiati",
276 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Domiati_cheese.jpg/270px-Domiati_cheese.jpg",
277 | "alt": "Domiati"
278 | },
279 | {
280 | "combination": ["Austin", "Hamburger", "Traditional"],
281 | "text": "Bryndza",
282 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Bryndza.jpg/270px-Bryndza.jpg",
283 | "alt": "Bryndza"
284 | },
285 | {
286 | "combination": ["Austin", "Hamburger", "Modern"],
287 | "text": "Grevé",
288 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Wedge_of_Swedish_Grev%C3%A9_cheese.jpg/270px-Wedge_of_Swedish_Grev%C3%A9_cheese.jpg",
289 | "alt": "Grevé"
290 | },
291 | {
292 | "combination": ["Austin", "Hamburger", "Mountains"],
293 | "text": "Parenica",
294 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Parenica.jpg/270px-Parenica.jpg",
295 | "alt": "Parenica"
296 | },
297 | {
298 | "combination": ["Austin", "Hamburger", "House"],
299 | "text": "Korbáčiky",
300 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/%C3%9Aden%C3%BD_korb%C3%A1%C4%8Dik_%28Slovakia%29.jpg/270px-%C3%9Aden%C3%BD_korb%C3%A1%C4%8Dik_%28Slovakia%29.jpg",
301 | "alt": "Korbáčiky"
302 | },
303 | {
304 | "combination": ["Portland", "Pizza", "Mountains"],
305 | "text": "Liptauer",
306 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Liptauer.jpg/270px-Liptauer.jpg",
307 | "alt": "Liptauer"
308 | },
309 | {
310 | "combination": ["Portland", "Pizza", "House"],
311 | "text": "Tvorog",
312 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Tvorog.jpg/270px-Tvorog.jpg",
313 | "alt": "Tvorog"
314 | },
315 | {
316 | "combination": ["Portland", "Pasta", "Traditional"],
317 | "text": "Circassian",
318 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/%D0%90%D0%B4%D1%8B%D0%B3%D0%B5%D0%B9%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D1%8B%D1%80.jpg",
319 | "alt": "Circassian"
320 | },
321 | {
322 | "combination": ["Portland", "Pasta", "Modern"],
323 | "text": "Cașcaval",
324 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Penteleu.jpg",
325 | "alt": "Cașcaval"
326 | },
327 | {
328 | "combination": ["Portland", "Pasta", "Mountains"],
329 | "text": "Telemea",
330 | "image": "https://upload.wikimedia.org/wikipedia/commons/2/29/Telemea.jpg",
331 | "alt": "Telemea"
332 | },
333 | {
334 | "combination": ["Portland", "Pasta", "House"],
335 | "text": "Requeijão",
336 | "image": "https://upload.wikimedia.org/wikipedia/commons/6/63/RequeijaoCremoso.jpg",
337 | "alt": "Requeijão"
338 | },
339 | {
340 | "combination": ["Portland", "Sandwich", "Traditional"],
341 | "text": "Norvegia",
342 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Norvegia_Vellagret.JPG",
343 | "alt": "Norvegia"
344 | },
345 | {
346 | "combination": ["Portland", "Sandwich", "Modern"],
347 | "text": "Jarlsberg",
348 | "image": "https://upload.wikimedia.org/wikipedia/commons/3/36/Jarlsberg_cheese.jpg",
349 | "alt": "Jarlsberg"
350 | },
351 | {
352 | "combination": ["Portland", "Sandwich", "House"],
353 | "text": "Gamalost",
354 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Gamalost-NorwegianOldCheese.jpg",
355 | "alt": "Gamalost"
356 | },
357 | {
358 | "combination": ["Portland", "Hamburger", "Traditional"],
359 | "text": "Brunost",
360 | "image": "https://upload.wikimedia.org/wikipedia/commons/c/c5/Brunost.jpg",
361 | "alt": "Brunost"
362 | },
363 | {
364 | "combination": ["Portland", "Hamburger", "Modern"],
365 | "text": "Pannónia",
366 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/Emmental_015.jpg",
367 | "alt": "Pannónia"
368 | },
369 | {
370 | "combination": ["Portland", "Hamburger", "Mountains"],
371 | "text": "Orda",
372 | "image": "https://upload.wikimedia.org/wikipedia/commons/4/46/Urd%C4%83.png",
373 | "alt": "Orda"
374 | },
375 | {
376 | "combination": ["Portland", "Hamburger", "House"],
377 | "text": "Trappista",
378 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/TrappistenKaeseOffen_1.jpg",
379 | "alt": "Trappista"
380 | },
381 | {
382 | "combination": ["New Orleans", "Pizza", "Mountains"],
383 | "text": "Myzithra",
384 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Homemade_Mizithra.jpg/2560px-Homemade_Mizithra.jpg",
385 | "alt": "Myzithra"
386 | },
387 | {
388 | "combination": ["New Orleans", "Pizza", "House"],
389 | "text": "Metsovone",
390 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/%CE%9C%CE%B5%CF%84%CF%83%CE%BF%CE%B2%CF%8C%CE%BD%CE%B5_6304.jpg",
391 | "alt": "Metsovone"
392 | },
393 | {
394 | "combination": ["New Orleans", "Pasta", "Traditional"],
395 | "text": "Manouri",
396 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/54/Manouri.jpg",
397 | "alt": "Manouri"
398 | },
399 | {
400 | "combination": ["New Orleans", "Pasta", "Modern"],
401 | "text": "Aura",
402 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f5/Aura_juusto.jpg",
403 | "alt": "Aura"
404 | },
405 | {
406 | "combination": ["New Orleans", "Pasta", "Mountains"],
407 | "text": "Saga",
408 | "image": "https://upload.wikimedia.org/wikipedia/commons/d/d0/Soft_and_creamy_Saga_cheese.jpg",
409 | "alt": "Saga"
410 | },
411 | {
412 | "combination": ["New Orleans", "Pasta", "House"],
413 | "text": "Danish Blue",
414 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Danish_Blue_cheese.jpg",
415 | "alt": "Danish Blue"
416 | },
417 | {
418 | "combination": ["New Orleans", "Sandwich", "Traditional"],
419 | "text": "Esrom",
420 | "image": "https://upload.wikimedia.org/wikipedia/commons/5/50/Esrom.jpg",
421 | "alt": "Esrom"
422 | },
423 | {
424 | "combination": ["New Orleans", "Sandwich", "Modern"],
425 | "text": "Staazer",
426 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Staazer_Heublumenk%C3%A4se_SB_01_WikiCheese_Lokal_K.jpg",
427 | "alt": "Staazer"
428 | },
429 | {
430 | "combination": ["New Orleans", "Sandwich", "Mountains"],
431 | "text": "Kashkaval",
432 | "image": "https://upload.wikimedia.org/wikipedia/commons/e/ee/Kaschkawal_Kashkaval_%D0%BA%D0%B0%D1%88%D0%BA%D0%B0%D0%B2%D0%B0%D0%BB_Balkank%C3%A4se_Sofia_IMG_7649.JPG",
433 | "alt": "Kashkaval"
434 | },
435 | {
436 | "combination": ["New Orleans", "Sandwich", "House"],
437 | "text": "Sura Kees",
438 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sura-Kees.JPG/1280px-Sura-Kees.JPG",
439 | "alt": "Sura Kees"
440 | },
441 | {
442 | "combination": ["New Orleans", "Hamburger", "Traditional"],
443 | "text": "Paneer",
444 | "image": "https://images.unsplash.com/photo-1631452180519-c014fe946bc7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=987&q=80",
445 | "alt": "Paneer"
446 | },
447 | {
448 | "combination": ["New Orleans", "Hamburger", "Modern"],
449 | "text": "Camembert",
450 | "image": "https://images.unsplash.com/photo-1634487359989-3e90c9432133?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1064&q=80",
451 | "alt": "Camembert"
452 | },
453 | {
454 | "combination": ["New Orleans", "Hamburger", "Mountains"],
455 | "text": "Sainte-Colome",
456 | "image": "https://images.unsplash.com/photo-1486297678162-eb2a19b0a32d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2073&q=80",
457 | "alt": "sanite colome"
458 | }
459 | ]
460 | }
461 |
--------------------------------------------------------------------------------
/server.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from 'express'
2 | import axios, { AxiosResponse } from 'axios'
3 | import { QuizData } from './interfaces'
4 | import * as dotenv from 'dotenv'
5 | dotenv.config()
6 |
7 | const PORT = 8000
8 | const app = express()
9 |
10 | app.get('/quiz-item', async (req: Request, res: Response) => {
11 | try {
12 | // @ts-ignore
13 | const response: AxiosResponse = await axios.get(process.env.URL, {
14 | headers: {
15 | 'X-Cassandra-Token': process.env.TOKEN,
16 | accept: 'application/json'
17 | }
18 | })
19 | if (response.status === 200) {
20 | const quizItem: QuizData = await response.data.data['20bef6ab-f035-4c99-9fcc-3cf19e13b70e']
21 | res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
22 | res.send(quizItem)
23 | }
24 | } catch (err) {
25 | console.error(err)
26 | }
27 | })
28 |
29 | app.listen(PORT, () => console.log('server is running on port ' + PORT))
30 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, createRef } from 'react'
2 | import Title from './components/Title'
3 | import QuestionsBlock from "./components/QuestionsBlock"
4 | import AnswerBlock from "./components/AnswerBlock"
5 | import { QuizData, Content } from '../interfaces'
6 |
7 | const App = () => {
8 | const [quiz, setQuiz] = useState()
9 | const [chosenAnswerItems, setChosenAnswerItems] = useState([])
10 | const [ unansweredQuestionIds, setUnansweredQuestionIds] = useState([])
11 | const [showAnswer, setShowAnswer] = useState(false)
12 |
13 | type ReduceType = {
14 | id?: {}
15 | }
16 | const refs = unansweredQuestionIds?.reduce((acc, id) => {
17 | acc[id as unknown as keyof ReduceType] = createRef()
18 | return acc
19 | }, {})
20 |
21 | const answerRef = createRef()
22 |
23 | const fetchData = async () => {
24 | try {
25 | const response = await fetch('http://localhost:8000/quiz-item')
26 | const json = await response.json()
27 | setQuiz(json)
28 | } catch ( err) {
29 | console.error(err)
30 | }
31 | }
32 |
33 | useEffect(() => {
34 | fetchData()
35 | } , [])
36 |
37 | useEffect(() => {
38 | const unansweredIds = quiz?.content?.map(({id} : Content) => id)
39 | setUnansweredQuestionIds(unansweredIds)
40 | }, [quiz])
41 |
42 |
43 | useEffect(() => {
44 | if (chosenAnswerItems.length > 0 && unansweredQuestionIds) {
45 | if (showAnswer && answerRef.current) {
46 | answerRef.current.scrollIntoView({behavior: 'smooth'})
47 | }
48 |
49 | if (unansweredQuestionIds.length <= 0 && chosenAnswerItems.length >= 1) {
50 | setShowAnswer(true)
51 | } else {
52 | const highestId = Math.min(...unansweredQuestionIds)
53 | refs[highestId].current.scrollIntoView({behavior: 'smooth'})
54 | }
55 | }
56 |
57 |
58 | }, [unansweredQuestionIds, chosenAnswerItems.length, showAnswer, answerRef.current, refs])
59 |
60 | return (
61 |
62 |
63 | {refs && quiz?.content.map((content: Content) => (
64 |
73 | ))}
74 | {showAnswer &&
75 | }
80 |
81 | )
82 | }
83 |
84 | export default App
85 |
--------------------------------------------------------------------------------
/src/components/AnswerBlock.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, forwardRef} from 'react'
2 | import { Answer } from '../../interfaces'
3 |
4 | const AnswerBlock = ({
5 | answerOptions,
6 | chosenAnswers
7 | } : {
8 | answerOptions: Answer[] | undefined,
9 | chosenAnswers: string[]
10 | }, ref: HTMLDivElement | any) => {
11 | const [result, setResult] = useState()
12 |
13 | useEffect(() => {
14 | answerOptions?.forEach((answer: Answer) => {
15 | if (
16 | chosenAnswers.includes(answer.combination[0]) &&
17 | chosenAnswers.includes(answer.combination[1]) &&
18 | chosenAnswers.includes(answer.combination[2])
19 | ) {
20 | setResult(answer)
21 | }
22 | })
23 | }, [chosenAnswers])
24 |
25 | console.log(result)
26 |
27 | return (
28 |
29 |
{result?.text}
30 |

31 |
32 | )
33 | }
34 |
35 | export default forwardRef(AnswerBlock)
36 |
--------------------------------------------------------------------------------
/src/components/QuestionBlock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Question } from '../../interfaces'
3 |
4 | const QuestionBlock = ({
5 | quizItemId,
6 | question,
7 | chosenAnswerItems,
8 | setChosenAnswerItems,
9 | unansweredQuestionIds,
10 | setUnansweredQuestionIds
11 | }: {
12 | quizItemId: number;
13 | question: Question,
14 | chosenAnswerItems: string[],
15 | setChosenAnswerItems: Function,
16 | unansweredQuestionIds: number[] | undefined,
17 | setUnansweredQuestionIds: Function
18 | }) => {
19 |
20 |
21 | const handleClick = () => {
22 | setChosenAnswerItems((prevState: string[]) => [...prevState, question.text])
23 | setUnansweredQuestionIds(unansweredQuestionIds?.filter((id: number) => id !== quizItemId))
24 | }
25 |
26 | const validPick = !chosenAnswerItems?.includes(question.text) &&
27 | !unansweredQuestionIds?.includes(quizItemId)
28 |
29 |
30 | return (
31 |
44 | )
45 | }
46 |
47 | export default QuestionBlock
48 |
--------------------------------------------------------------------------------
/src/components/QuestionsBlock.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react'
2 | import { Content, Question } from '../../interfaces'
3 | import QuestionBlock from './QuestionBlock'
4 |
5 | const QuestionsBlock = ({
6 | quizItem,
7 | chosenAnswerItems,
8 | setChosenAnswerItems,
9 | unansweredQuestionIds,
10 | setUnansweredQuestionIds,
11 | } : {
12 | quizItem: Content,
13 | chosenAnswerItems: string[],
14 | setChosenAnswerItems: Function,
15 | unansweredQuestionIds: number[] | undefined
16 | setUnansweredQuestionIds: Function
17 | }, ref: React.LegacyRef | undefined ) => {
18 | return (
19 | <>
20 | {quizItem.text}
21 |
22 | {quizItem?.questions.map((question: Question, _index: number) =>(
23 |
32 | ))}
33 |
34 | >
35 | )
36 | }
37 |
38 | export default forwardRef(QuestionsBlock)
39 |
--------------------------------------------------------------------------------
/src/components/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { QuizData } from "../../interfaces"
3 |
4 | const Title = ({ title, subtitle } : {
5 | title: QuizData['title'] | undefined,
6 | subtitle: QuizData['subtitle'] | undefined,
7 | }) => {
8 | return (
9 |
10 |
{title}
11 |
{subtitle}
12 |
13 | )
14 | }
15 |
16 | export default Title
17 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;400;500&display=swap');
2 |
3 | * {
4 | font-family: 'Montserrat', sans-serif;
5 | }
6 |
7 | body {
8 | display: flex;
9 | justify-content: center;
10 | }
11 |
12 | .app {
13 | width: 37.5rem;
14 | }
15 |
16 | h1 {
17 | font-size: 2.5rem;
18 | line-height: 1.05;
19 | text-align: left;
20 | }
21 |
22 | h2 {
23 | font-size: 3.5rem;
24 | line-height: 1.05;
25 | text-align: center;
26 | }
27 |
28 | h3 {
29 | margin: 0.625rem;
30 | }
31 |
32 | p {
33 | font-size: 1.125rem;
34 | line-height: 1.2;
35 | }
36 |
37 | .title-block {
38 | width: 37.5rem;
39 | height: 13.125rem;
40 | background-color: rgb(102, 69, 221);
41 | border-radius: 0.313rem;
42 | display: flex;
43 | justify-content: center;
44 | align-items: center;
45 | color: rgb(255,255,255);
46 | }
47 |
48 | .questions-container {
49 | display: flex;
50 | flex-wrap: wrap;
51 | justify-content: space-between;
52 | }
53 |
54 | .question-block {
55 | width: 17.875rem;
56 | overflow: hidden;
57 | background-color: rgb(255,255,255);
58 | border-radius: 0.313rem;
59 | box-shadow: rgba(0,0,0,0.07) 0 0 0 0.063rem;
60 | text-align: center;
61 | margin-bottom: 0.938rem;
62 | border: none;
63 | padding: 0;
64 | }
65 |
66 | .question-block p {
67 | font-size: 0.8rem;
68 | font-style: italic;
69 | }
70 |
71 | .question-block a {
72 | color: rgb(135, 135, 135);
73 | text-decoration: none;
74 | }
75 |
76 | .answer-block {
77 | width: 36.5rem;
78 | background-color: rgb(255,61,158);
79 | border-radius: 0.313rem;
80 | display: flex;
81 | align-items: center;
82 | flex-direction: column;
83 | color: rgb(255,255,255);
84 | }
85 |
86 | .answer-block img {
87 | width: 90%;
88 | }
89 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import './index.css'
4 | import App from './App'
5 |
6 | const root = ReactDOM.createRoot(
7 | document.getElementById('root') as HTMLElement
8 | )
9 | root.render(
10 |
11 |
12 |
13 | )
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "commonjs",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------