Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"axios": "0.21.0",
"classnames": "2.2.6",
"cross-env": "7.0.2",
"express": "4.17.1",
"fontfaceobserver": "2.1.0",
"husky": "4.3.0",
"lodash": "4.17.20",
Expand Down Expand Up @@ -63,6 +64,7 @@
},
"scripts": {
"start": "react-scripts start",
"start:server": "node server.ts",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
Expand Down Expand Up @@ -124,5 +126,5 @@
}
}
},
"proxy":"https://openapi.naver.com"
"proxy": "http://localhost:4000"
}
23 changes: 23 additions & 0 deletions server.ts
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, () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api 요청을 위한 서버를 따로 둔 이유가 궁금합니당

console.log('Server is running');
});
189 changes: 187 additions & 2 deletions src/App.tsx
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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최근에 알았던 건데 Math.floor(..) 를 더블 틸드 ~~(..) 로 대체해서 사용할 수 있더라구요! 물론 스타일의 문제지만, 꽤나 흥미로웠던 연산이라 적습니다 ㅎㅎ


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++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forEach 를 쓰면 코드가 더 간결해질 것 같아요! 제가 forEach 신봉자라 ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

간단한 연산에서는 가독성을 선택하면 forEach쓰는것도 좋은거같아요! 저는 성능 때문에 일반적으로 for를 더 자주쓰게 되더라구요
경우에 따라 다르지만 for가 forEach에 비해 2배정도 성능이 좋다고 해요

Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

랜덤함수가 많이 나오는데 따로 훅으로 빼도 좋을 것 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네요! 의식의 흐름대로 짜다보니 랜덤함수가 너무 많아졌네요

const ingredientsList = [
...quizIngredients.slice(0, answerIndex),
answerIngredient,
...quizIngredients.slice(answerIndex),
];
Copy link

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 친구도 macgic number 처리해주면 좋을 것 같아요 ㅎㅎ

},
[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;
Empty file removed src/components/.gitkeep
Empty file.
14 changes: 14 additions & 0 deletions src/components/FoodCard/FoodCard.styled.ts
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;
`;
22 changes: 22 additions & 0 deletions src/components/FoodCard/FoodCard.tsx
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;
1 change: 1 addition & 0 deletions src/components/FoodCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './FoodCard';
29 changes: 29 additions & 0 deletions src/components/Ingredients/Ingredients.styled.ts
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;
`;
37 changes: 37 additions & 0 deletions src/components/Ingredients/Ingredients.tsx
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;
1 change: 1 addition & 0 deletions src/components/Ingredients/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Ingredients';
37 changes: 37 additions & 0 deletions src/components/LoadingResult/LoadingResult.styled.ts
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;
}
`;
Loading