-
Notifications
You must be signed in to change notification settings - Fork 0
qux팀 요리퀴즈 #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: week4-qux
Are you sure you want to change the base?
qux팀 요리퀴즈 #7
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| const express = require('express'); | ||
| const http = require('http'); | ||
| const axios = require('axios'); | ||
|
|
||
| const app = express(); | ||
|
|
||
| app.get('/api/getRecipe/:recipeId', async (req, res) => { | ||
| const { recipeId } = req.params; | ||
|
|
||
| try { | ||
| const result = await axios.get( | ||
| `http://211.237.50.150:7080/openapi/f272a304bf6f5c342cd55e3630601b0e1dddd251d5d5f35823136edab357a410/json/Grid_20150827000000000227_1/1/1000?RECIPE_ID=${recipeId}`, | ||
| ); | ||
|
|
||
| res.send(result.data); | ||
| } catch (error) { | ||
| res.status(error.status || 500).send(error); | ||
| } | ||
| }); | ||
|
|
||
| http.createServer(app).listen(4000, () => { | ||
| console.log('Server is running'); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,193 @@ | ||
| /* External dependencies */ | ||
| import React from 'react'; | ||
| import React, { useCallback, useEffect, useState } from 'react'; | ||
| import axios from 'axios'; | ||
| import _ from 'lodash'; | ||
|
|
||
| /* Internal dependencies */ | ||
| import Quiz from 'components/Quiz'; | ||
| import Result from 'components/Result'; | ||
| import LoadingResult from 'components/LoadingResult'; | ||
| import recipesJSON from 'constants/recipes.json'; | ||
|
|
||
| const QUIZ_COUNT = 10; | ||
|
|
||
| //난이도 하 | ||
| const QUIZ_SELECT_COUNT = 4; | ||
| export const IMAGE_WIDTH = 600; | ||
|
|
||
| // 난이도 중 | ||
| // const QUIZ_SELECT_COUNT = 5; | ||
| // export const IMAGE_WIDTH = 400; | ||
|
|
||
| // 난이도 상 | ||
| // const QUIZ_SELECT_COUNT = 6; | ||
| // export const IMAGE_WIDTH = 200; | ||
|
|
||
| // 난이도 특상 | ||
| // const QUIZ_SELECT_COUNT = 10; | ||
| // export const IMAGE_WIDTH = 80; | ||
|
Comment on lines
+14
to
+28
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주석 바꿔서 난이도 조절가능! |
||
|
|
||
| const recipes = recipesJSON.Grid_20150827000000000226_1.row; | ||
|
|
||
| function getRandomNumbers( | ||
| startIndex: number, | ||
| endIndex: number, | ||
| amount: number, | ||
| ) { | ||
| const tempArr: number[] = []; | ||
| if (startIndex > endIndex) { | ||
| let temp = startIndex; | ||
| startIndex = endIndex; | ||
| endIndex = temp; | ||
|
Comment on lines
+39
to
+41
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거는 [startIndex, endIndex] = [endIndex, startIndex]; 로 줄일 수 있을 것 같아요! |
||
| } | ||
| if (endIndex - startIndex + 1 < amount) { | ||
| amount = endIndex - startIndex + 1; | ||
| } | ||
|
|
||
| while (tempArr.length < amount) { | ||
| const randomIndex = | ||
| Math.floor(Math.random() * endIndex - startIndex + 1) + startIndex; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 최근에 알았던 건데 |
||
|
|
||
| if (!tempArr.includes(randomIndex)) { | ||
| tempArr.push(randomIndex); | ||
| } | ||
| } | ||
|
|
||
| return tempArr; | ||
| } | ||
|
|
||
| function App() { | ||
| return <div>Hello World!</div>; | ||
| const [answerCount, setAnswerCount] = useState(0); | ||
| const [currentQuizIndex, setCurrentQuizIndex] = useState(1); | ||
| const [isLoading, setLoading] = useState(false); | ||
| const [corrected, setCorrected] = useState(false); | ||
| const [isFinished, setFinished] = useState(false); | ||
| const [recipeName, setRecipeName] = useState(''); | ||
| const [recipeUrl, setRecipeUrl] = useState(''); | ||
| const [answer, setAnswer] = useState(''); | ||
| const [ingredients, setIngredients] = useState<string[]>([]); | ||
|
|
||
| const handleQuiz = useCallback(async () => { | ||
| try { | ||
| const quizIndex = Math.floor(Math.random() * recipes.length); | ||
| const { RECIPE_ID, RECIPE_NM_KO, IMG_URL } = recipes[quizIndex]; | ||
|
|
||
| const result = await axios.get(`/api/getRecipe/${RECIPE_ID}`); | ||
| const resultRow = _.get(result, 'data.Grid_20150827000000000227_1.row'); | ||
|
|
||
| if (resultRow.length < QUIZ_SELECT_COUNT) { | ||
| handleQuiz(); | ||
| return; | ||
| } | ||
|
|
||
| let answerQuizIndex = -1; | ||
|
|
||
| while (answerQuizIndex === -1) { | ||
| const tempIndex = Math.floor(Math.random() * recipes.length); | ||
|
|
||
| if (tempIndex !== quizIndex) { | ||
| answerQuizIndex = tempIndex; | ||
| } | ||
| } | ||
|
|
||
| const { RECIPE_ID: answerRecipeId } = recipes[answerQuizIndex]; | ||
|
|
||
| const answerResult = await axios.get(`/api/getRecipe/${answerRecipeId}`); | ||
| const answerResultRow = _.get( | ||
| answerResult, | ||
| 'data.Grid_20150827000000000227_1.row', | ||
| ); | ||
|
|
||
| if (!_.isArray(resultRow) || !_.isArray(answerResultRow)) { | ||
| throw new Error(); | ||
| } | ||
|
|
||
| const allIngredients: string[] = resultRow.map(row => row.IRDNT_NM); | ||
| const allAnswerIngredients: string[] = answerResultRow.map( | ||
| row => row.IRDNT_NM, | ||
| ); | ||
|
|
||
| let answerIngredient = ''; | ||
|
|
||
| while (_.isEmpty(answerIngredient)) { | ||
| const answerIngredientIndex = Math.floor( | ||
| Math.random() * allAnswerIngredients.length, | ||
| ); | ||
| const tempAnswerIngredient = | ||
| allAnswerIngredients[answerIngredientIndex]; | ||
|
|
||
| if (!allIngredients.includes(tempAnswerIngredient)) { | ||
| answerIngredient = tempAnswerIngredient; | ||
| } | ||
| } | ||
|
|
||
| const quizIngredients: string[] = []; | ||
| const quizIngredientIndexes = getRandomNumbers( | ||
| 0, | ||
| allIngredients.length - 1, | ||
| QUIZ_SELECT_COUNT - 1, | ||
| ); | ||
|
|
||
| for (let i = 0; i < quizIngredientIndexes.length; i++) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 간단한 연산에서는 가독성을 선택하면 forEach쓰는것도 좋은거같아요! 저는 성능 때문에 일반적으로 for를 더 자주쓰게 되더라구요
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㅇㅁㅇ 헉 처음알았어요! |
||
| quizIngredients.push(allIngredients[quizIngredientIndexes[i]]); | ||
| } | ||
|
|
||
| const answerIndex = Math.floor(Math.random() * QUIZ_SELECT_COUNT); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 랜덤함수가 많이 나오는데 따로 훅으로 빼도 좋을 것 같아요!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋네요! 의식의 흐름대로 짜다보니 랜덤함수가 너무 많아졌네요 |
||
| const ingredientsList = [ | ||
| ...quizIngredients.slice(0, answerIndex), | ||
| answerIngredient, | ||
| ...quizIngredients.slice(answerIndex), | ||
| ]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. splice를 쓰면 slice를 2번 사용하지 않고 추가할 수 있을거 같네요! |
||
|
|
||
| setRecipeName(RECIPE_NM_KO); | ||
| setRecipeUrl(IMG_URL); | ||
| setAnswer(answerIngredient); | ||
| setIngredients(ingredientsList); | ||
| } catch (error) { | ||
| console.log(error); | ||
| } | ||
| }, []); | ||
|
|
||
| const handleAnswer = useCallback( | ||
| (selectedAnswer: string) => { | ||
| setLoading(true); | ||
|
|
||
| if (answer === selectedAnswer) { | ||
| setAnswerCount(prev => prev + 1); | ||
| setCorrected(true); | ||
| } else { | ||
| setCorrected(false); | ||
| } | ||
|
|
||
| setTimeout(() => { | ||
| if (currentQuizIndex >= QUIZ_COUNT) { | ||
| setFinished(true); | ||
| } | ||
|
|
||
| setLoading(false); | ||
| setCurrentQuizIndex(prev => prev + 1); | ||
| handleQuiz(); | ||
| }, 2000); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 친구도 |
||
| }, | ||
| [answer, currentQuizIndex, handleQuiz], | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| handleQuiz(); | ||
| }, [handleQuiz]); | ||
|
|
||
| return ( | ||
| <> | ||
| <Quiz | ||
| recipeName={recipeName} | ||
| recipeUrl={recipeUrl} | ||
| ingredients={ingredients} | ||
| onAnswer={handleAnswer} | ||
| /> | ||
| {isFinished && <Result answerCount={answerCount} />} | ||
| {isLoading && <LoadingResult corrected={corrected} answer={answer} />} | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default App; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* External dependencies */ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const FoodCardWrapper = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| `; | ||
|
|
||
| export const RecipeName = styled.p` | ||
| margin-top: 0; | ||
| font-size: 32px; | ||
| font-weight: bolder; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* External dependencies */ | ||
| import React from 'react'; | ||
|
|
||
| /* Internal dependencies */ | ||
| import { IMAGE_WIDTH } from 'App'; | ||
| import * as Styled from './FoodCard.styled'; | ||
|
|
||
| interface FoodCardProps { | ||
| recipeName: string; | ||
| recipeUrl: string; | ||
| } | ||
|
|
||
| const FoodCard = ({ recipeName, recipeUrl }: FoodCardProps) => { | ||
| return ( | ||
| <Styled.FoodCardWrapper> | ||
| <Styled.RecipeName>{recipeName}</Styled.RecipeName> | ||
| <img src={recipeUrl} width={IMAGE_WIDTH} alt="" /> | ||
| </Styled.FoodCardWrapper> | ||
| ); | ||
| }; | ||
|
|
||
| export default FoodCard; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default } from './FoodCard'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* External dependencies */ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const IngredientItem = styled.div` | ||
| padding: 12px 30px; | ||
| font-size: 24px; | ||
| font-weight: bolder; | ||
| background-color: #fcfcfc; | ||
| border: 2px solid #d2d2d2; | ||
| border-radius: 8px; | ||
| cursor: pointer; | ||
|
|
||
| &:hover { | ||
| background-color: #f2f2f2; | ||
| } | ||
| `; | ||
|
|
||
| export const IngredientList = styled.div` | ||
| displat: flex; | ||
| flex-direction: column; | ||
|
|
||
| ${IngredientItem} + ${IngredientItem} { | ||
| margin-top: 20px; | ||
| } | ||
| `; | ||
|
|
||
| export const ItemNumber = styled.span` | ||
| margin-right: 10px; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /* External dependencies */ | ||
| import React, { useCallback } from 'react'; | ||
|
|
||
| /* Internal dependencies */ | ||
| import * as Styled from './Ingredients.styled'; | ||
|
|
||
| interface IngredientsProps { | ||
| ingredients: string[]; | ||
| onAnswer: (selectedAnswer: string) => void; | ||
| } | ||
|
|
||
| const Ingredients = ({ ingredients, onAnswer }: IngredientsProps) => { | ||
| const handleAnswer = useCallback( | ||
| (event: React.MouseEvent<HTMLDivElement>) => { | ||
| const { ingredient } = event.currentTarget.dataset; | ||
| onAnswer(ingredient as string); | ||
| }, | ||
| [onAnswer], | ||
| ); | ||
|
|
||
| return ( | ||
| <Styled.IngredientList> | ||
| {ingredients.map((ingredient, index) => ( | ||
| <Styled.IngredientItem | ||
| key={`${ingredient}-${index}`} | ||
| data-ingredient={ingredient} | ||
| onClick={handleAnswer} | ||
| > | ||
| <Styled.ItemNumber>{index + 1}</Styled.ItemNumber> | ||
| {ingredient} | ||
| </Styled.IngredientItem> | ||
| ))} | ||
| </Styled.IngredientList> | ||
| ); | ||
| }; | ||
|
|
||
| export default Ingredients; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default } from './Ingredients'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /* External dependencies */ | ||
| import styled from 'styled-components'; | ||
|
|
||
| interface CorrectedProps { | ||
| corrected: boolean; | ||
| } | ||
|
|
||
| export const ResultWrapper = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| width: 100vw; | ||
| height: 100vh; | ||
| position: fixed; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
| bottom: 0; | ||
| opacity: 0.95; | ||
| background-color: #ffffff; | ||
| z-index: 99999999; | ||
| `; | ||
|
|
||
| export const Corrected = styled.div<CorrectedProps>` | ||
| font-size: 60px; | ||
| font-weight: bolder; | ||
| color: ${({ corrected }) => (corrected ? 'green' : 'red')}; | ||
| `; | ||
|
|
||
| export const Answer = styled.div` | ||
| font-size: 28px; | ||
|
|
||
| span { | ||
| color: blue; | ||
| } | ||
| `; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api 요청을 위한 서버를 따로 둔 이유가 궁금합니당