diff --git a/.eslintrc.js b/.eslintrc.js index 66385bf..d1508bb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,6 +42,6 @@ module.exports = { 'react/function-component-definition': 'off', 'react/require-default-props': 'off', 'no-plusplus': 'off', - 'no-param-reassign': 'off,', + 'no-param-reassign': 'off', }, }; diff --git a/package.json b/package.json index 3ce0881..5c0ab4a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@svgr/webpack": "^5.5.0", "@types/react-redux": "^7.1.23", "@types/yup": "^0.29.13", + "axios": "^0.26.1", "emotion-normalize": "^11.0.1", "firebase": "^9.6.8", "formik": "^2.2.9", diff --git a/src/api/requestData.ts b/src/api/requestData.ts new file mode 100644 index 0000000..bae0585 --- /dev/null +++ b/src/api/requestData.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; + +const apiUrl = 'https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/'; + +export const getRandomRecipe = async (number = 1) => { + try { + const { data } = await axios.get(`${apiUrl}/recipes/random`, { + headers: { + 'content-type': 'application/json', + 'x-rapidapi-host': `${process.env.NEXT_PUBLIC_RAPID_API_HOST}`, + 'x-rapidapi-key': `${process.env.NEXT_PUBLIC_RAPID_API_KEY}`, + }, + params: { number }, + }); + return data; + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(e.message); + } else { + throw new Error('different error than axios'); + } + } +}; diff --git a/src/components/Card/Card.stories.tsx b/src/components/Card/Card.stories.tsx index 6138256..4787f5e 100644 --- a/src/components/Card/Card.stories.tsx +++ b/src/components/Card/Card.stories.tsx @@ -8,9 +8,10 @@ export default { id: 1, type: 'wide', background: 'white', - hasSummary: true, + hasSummary: false, headingPosition: 'bottomLeft', - title: 'hi', + title: 'image', + // imgSrc: 'https://spoonacular.com/recipeImages/Chopped-Kale-Salad-with-Pomegranate---Avocado-584495.jpg', }, parameters: { nextRouter: { diff --git a/src/components/Card/Card.styled.tsx b/src/components/Card/Card.styled.tsx index 31fab7a..876574a 100644 --- a/src/components/Card/Card.styled.tsx +++ b/src/components/Card/Card.styled.tsx @@ -96,6 +96,8 @@ export const CardContainer = styled.div` `; export const CardFigureImgContainer = styled.div` + position: relative; + ${({ $type }) => typeCss[$type]} `; diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 172d09b..2eee2f5 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,7 +1,5 @@ import Image from 'next/image'; import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useState } from 'react'; import { HiCursorClick } from 'react-icons/hi'; import { excludeTags } from 'utils'; import { @@ -21,7 +19,6 @@ export const Card = ({ background, hasSummary, headingPosition, - // image, imgSrc = '/images/no-image.jpg', title, summary = '', @@ -42,7 +39,7 @@ export const Card = ({ {excludeTags(summary) .split('. ') .map((text, index, texts) => ( - {text + (index < texts.length - 1 ? '.' : '')} + {text + (index < texts.length - 1 ? '.' : '')} ))} diff --git a/src/components/CookingInfo/CookingInfo.styled.tsx b/src/components/CookingInfo/CookingInfo.styled.tsx index 6da7b4f..4f78907 100644 --- a/src/components/CookingInfo/CookingInfo.styled.tsx +++ b/src/components/CookingInfo/CookingInfo.styled.tsx @@ -5,9 +5,12 @@ import { pxToRem } from 'utils'; export const StyledDL = styled.dl` color: #cbcbcb; - font-size: ${pxToRem(24)}; + font-size: ${pxToRem(18)}; padding: ${pxToRem(10)} 0; width: 100%; + + /* @media (min-width: 380ㅔ) {} + } */ `; export const StyledDiv = styled.div` diff --git a/src/components/HotRecipes/HotRecipes.styled.tsx b/src/components/HotRecipes/HotRecipes.styled.tsx index d6741ec..27f9b14 100644 --- a/src/components/HotRecipes/HotRecipes.styled.tsx +++ b/src/components/HotRecipes/HotRecipes.styled.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; -import { Heading } from '..'; import { media, pxToRem } from 'utils'; +import { Heading } from '..'; export const HotRecipesSection = styled.section` ${media.desktop} { diff --git a/src/components/HotRecipes/HotRecipes.tsx b/src/components/HotRecipes/HotRecipes.tsx index 031e998..e95e0a7 100644 --- a/src/components/HotRecipes/HotRecipes.tsx +++ b/src/components/HotRecipes/HotRecipes.tsx @@ -1,27 +1,26 @@ -import { SkeletonCard, Card } from '..'; import { useHotRecipes } from 'hooks'; +import { SkeletonCard, Card } from '..'; import { HotRecipesCardItem, HotRecipesCardList, HotRecipesHeader, HotRecipesSection } from './HotRecipes.styled'; export const HotRecipes = () => { const { hotRecipes, loading } = useHotRecipes(); - //TODO: customAPi 완료후 여기에 타입 달아주기 + // TODO: customAPi 완료후 여기에 타입 달아주기 const renderCards = ({ recipeId, image, title }) => { if (loading) { return ; - } else { - return ( - - ); } + return ( + + ); }; return ( diff --git a/src/components/Loading/Loading.tsx b/src/components/Loading/Loading.tsx index 05b7570..518c861 100644 --- a/src/components/Loading/Loading.tsx +++ b/src/components/Loading/Loading.tsx @@ -8,17 +8,21 @@ const loadingEndNode = $('#loading-end'); export const Loading = ({ message, showBackground }: LoadingProps) => { useEffect(() => { - loadingStartNode.setAttribute('role', 'alert'); - loadingStartNode.insertAdjacentHTML('beforeend', `${message}`); + if (loadingStartNode && loadingEndNode) { + loadingStartNode.setAttribute('role', 'alert'); + loadingStartNode.insertAdjacentHTML('beforeend', `${message}`); + } return () => { - loadingStartNode.removeAttribute('role'); - loadingStartNode.innerHTML = ''; + if (loadingStartNode && loadingEndNode) { + loadingStartNode.removeAttribute('role'); + loadingStartNode.innerHTML = ''; - loadingEndNode.insertAdjacentHTML('beforeend', `Finished Loading.`); - setTimeout(() => { - loadingEndNode.innerHTML = ''; - }, 800); + loadingEndNode.insertAdjacentHTML('beforeend', `Finished Loading.`); + setTimeout(() => { + loadingEndNode.innerHTML = ''; + }, 800); + } }; }, [message]); diff --git a/src/components/RandomRecipe/RandomRecipe.styled.tsx b/src/components/RandomRecipe/RandomRecipe.styled.tsx index 8dfad1d..83e21ea 100644 --- a/src/components/RandomRecipe/RandomRecipe.styled.tsx +++ b/src/components/RandomRecipe/RandomRecipe.styled.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; -import { Button } from '../'; import { media, pxToRem } from 'utils'; import { GiPerspectiveDiceSixFacesRandom } from 'react-icons/gi'; +import { Button } from '..'; export const RandomRecipeSection = styled.section` flex-grow: 1; @@ -18,24 +18,22 @@ export const RandomRecipeSection = styled.section` `; export const RandomRecipeButton = styled(Button)` - display: flex; - align-items: center; - gap: 10px; - position: absolute; - top: 0; - right: 0; - margin: ${pxToRem(16)}; - ${media.mobile} { margin: ${pxToRem(8)} 0 ${pxToRem(16)} ${pxToRem(16)}; font-size: ${pxToRem(12)}; - padding: ${pxToRem(1)} ${pxToRem(6)}; } ${media.desktop} { margin: ${pxToRem(36)} 0 ${pxToRem(36)} ${pxToRem(36)}; - padding: ${pxToRem(6)} ${pxToRem(18)}; } + + display: flex; + align-items: center; + gap: 10px; + position: absolute; + top: 0; + right: 0; + margin: ${pxToRem(16)}; `; export const RandomDiceIcon = styled(GiPerspectiveDiceSixFacesRandom)` diff --git a/src/components/RandomRecipe/RandomRecipe.tsx b/src/components/RandomRecipe/RandomRecipe.tsx index e1f4a98..a6d3f84 100644 --- a/src/components/RandomRecipe/RandomRecipe.tsx +++ b/src/components/RandomRecipe/RandomRecipe.tsx @@ -1,5 +1,4 @@ import { useRandomRecipe } from 'hooks'; -import { RandomRecipe as RandomRecipeType } from 'store/services/types/queries'; import { Heading, SkeletonCard, Card } from '..'; import { RandomDiceIcon, @@ -9,29 +8,26 @@ import { } from './RandomRecipe.styled'; export const RandomRecipe = (): JSX.Element => { - const { recipe, error, isLoading, handleClick } = useRandomRecipe(); - - console.log(recipe); - console.log(error); + const { recipe, isLoading, handleClick } = useRandomRecipe(); const renderCard = () => { if (isLoading) { - return ; - } else { - const { id, title, summary, image } = recipe as RandomRecipeType; - return ( - - ); + return ; } + const { id, title, summary, image } = recipe; + + return ( + + ); }; return ( diff --git a/src/hooks/useRandomRecipe.ts b/src/hooks/useRandomRecipe.ts index b1ea182..1ce8e5b 100644 --- a/src/hooks/useRandomRecipe.ts +++ b/src/hooks/useRandomRecipe.ts @@ -1,36 +1,31 @@ import { useCallback, useEffect, useState } from 'react'; -import { useGetRandomRecipeQuery } from 'store/services'; import _ from 'lodash'; import { RandomRecipe } from 'store/services/types/queries'; +import { getRandomRecipe } from 'api/requestData'; export const useRandomRecipe = () => { - const [savedRecipe, setSavedRecipe] = useState({}); - const [recipe, setRecipe] = useState({}); - - const { data, error, isLoading } = useGetRandomRecipeQuery(2); + const [savedRecipe, setSavedRecipe] = useState({} as RandomRecipe); + const [recipe, setRecipe] = useState({} as RandomRecipe); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { - if (data) { - const { recipes } = data; + (async () => { + const { recipes } = await getRandomRecipe(2); + setRecipe(recipes[0]); setSavedRecipe(recipes[1]); - } - }, []); - const getRecipe = useCallback(() => { - setRecipe(savedRecipe); - const { data } = useGetRandomRecipeQuery(1); - if (data) { - const { recipes } = data; - setSavedRecipe(recipes[0]); - } + setIsLoading(false); + })(); }, []); - const handleClick = () => { - _.throttle(() => { - getRecipe(); - }, 300); + const getRecipe = async () => { + setRecipe(savedRecipe); + const { recipes } = await getRandomRecipe(); + setSavedRecipe(recipes[0]); }; - return { recipe, error, isLoading, handleClick }; + const handleClick = _.throttle(getRecipe, 300); + + return { recipe, isLoading, handleClick }; }; diff --git a/src/store/slices/recipeReducer.ts b/src/store/slices/recipeReducer.ts deleted file mode 100644 index aaa23ac..0000000 --- a/src/store/slices/recipeReducer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { RandomRecipe, RandomRecipeQuery } from 'store/services/types/queries'; - -interface RecipeState { - // savedRecipes: RandomRecipeQuery[]; - recipes: RandomRecipe; - // curIndex: number; -} - -// const initialState: RecipeState = { recipes: {} as RandomRecipe, curIndex: 0 }; - -// export const recipeSlice = createSlice({ -// name: 'recipe', -// initialState, -// reducers: { -// nextRandomRecipe(state, payload) { -// state.curIndex++; -// state.recipes = -// }, -// }, -// }); diff --git a/src/stories/example/button/button.css b/src/stories/example/button/button.css deleted file mode 100644 index dc91dc7..0000000 --- a/src/stories/example/button/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; -} -.storybook-button--primary { - color: white; - background-color: #1ea7fd; -} -.storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; -} -.storybook-button--small { - font-size: 12px; - padding: 10px 16px; -} -.storybook-button--medium { - font-size: 14px; - padding: 11px 20px; -} -.storybook-button--large { - font-size: 16px; - padding: 12px 24px; -} diff --git a/src/stories/example/button/button.jsx b/src/stories/example/button/button.jsx deleted file mode 100644 index 574991f..0000000 --- a/src/stories/example/button/button.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import './button.css'; -import React from 'react'; -import PropTypes from 'prop-types'; - -/** - * UI 인터랙션을 위한 기본 UI 컴포넌트 - */ -export const Button = ({ primary, backgroundColor, size, label, ...props }) => { - - const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; - - return ( - - ); -}; - -Button.propTypes = { - /** - * 프라이머리 버튼 설정 - */ - primary: PropTypes.bool, - /** - * 배경 색상 설정 - */ - backgroundColor: PropTypes.string, - /** - * 버튼 크기 설정 - */ - size: PropTypes.oneOf(['small', 'medium', 'large']), - /** - * 버튼 콘텐츠 - */ - label: PropTypes.string.isRequired, - /** - * 클릭 이벤트 핸들러 (옵션) - */ - onClick: PropTypes.func, -}; - -Button.defaultProps = { - backgroundColor: null, - primary: false, - size: 'medium', - onClick: undefined, -}; diff --git a/src/stories/example/button/button.stories.jsx b/src/stories/example/button/button.stories.jsx deleted file mode 100644 index a2c3ad2..0000000 --- a/src/stories/example/button/button.stories.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Button } from './button'; - -// 기본 내보내기 참고: https://storybook.js.org/docs/react/writing-stories/introduction#default-export -export default { - title: 'Example/Button', - component: Button, - - // decorators 참고: https://storybook.js.org/docs/react/writing-stories/decorators#component-decorators - // decorators: [(story) =>
{story()}
], - - // argTypes 참고: https://storybook.js.org/docs/react/api/argtypes - argTypes: { - backgroundColor: { control: 'color' }, - }, -}; - -// 컴포넌트 템플릿 참고: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Template = (args) =>