From b88ee376cb032b4b40c097daef6c106dd5a0c9da Mon Sep 17 00:00:00 2001 From: Taeeun Kim Date: Wed, 15 Nov 2023 20:18:14 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[FE]=20feat:=20=EB=A6=AC=EB=B7=B0,=20?= =?UTF-8?q?=EA=BF=80=EC=A1=B0=ED=95=A9=20=EC=A2=8B=EC=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=90=20=EB=82=99=EA=B4=80=EC=A0=81=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=A0=81=EC=9A=A9=20(#839)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 리뷰 좋아요에 낙관적 업데이트 적용 * feat: 리뷰 좋아요 버튼 컴포넌트 분리 * feat: 꿀조합 좋아요에 낙관적 업데이트 적용 * feat: client state로 좋아요를 관리하게끔 수정 * feat: 꿀조합 좋아요는 server state를 동기화하도록 수정 * style: console.log 제거 * feat: 토스트 에러 메시지 수정 * feat: 좋아요 개수도 업데이트 * fix: 객체 destructing이 안되어 수정 --- .../Recipe/RecipeFavorite/RecipeFavorite.tsx | 60 ---------------- .../RecipeFavoriteButton.tsx | 43 ++++++++++++ frontend/src/components/Recipe/index.ts | 2 +- .../ReviewFavoriteButton.tsx | 69 +++++++++++++++++++ .../Review/ReviewItem/ReviewItem.tsx | 59 ++-------------- frontend/src/components/Review/index.ts | 1 + .../recipe/useRecipeFavoriteMutation.ts | 37 +++++++++- .../review/useReviewFavoriteMutation.ts | 14 +++- frontend/src/pages/RecipeDetailPage.tsx | 4 +- 9 files changed, 171 insertions(+), 118 deletions(-) delete mode 100644 frontend/src/components/Recipe/RecipeFavorite/RecipeFavorite.tsx create mode 100644 frontend/src/components/Recipe/RecipeFavoriteButton/RecipeFavoriteButton.tsx create mode 100644 frontend/src/components/Review/ReviewFavoriteButton/ReviewFavoriteButton.tsx diff --git a/frontend/src/components/Recipe/RecipeFavorite/RecipeFavorite.tsx b/frontend/src/components/Recipe/RecipeFavorite/RecipeFavorite.tsx deleted file mode 100644 index 703e209c..00000000 --- a/frontend/src/components/Recipe/RecipeFavorite/RecipeFavorite.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { theme, Button, Text } from '@fun-eat/design-system'; -import { useState } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useTimeout } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; -import { useRecipeFavoriteMutation } from '@/hooks/queries/recipe'; - -interface RecipeFavoriteProps { - favorite: boolean; - favoriteCount: number; - recipeId: number; -} - -const RecipeFavorite = ({ recipeId, favorite, favoriteCount }: RecipeFavoriteProps) => { - const [isFavorite, setIsFavorite] = useState(favorite); - const [currentFavoriteCount, setCurrentFavoriteCount] = useState(favoriteCount); - const { toast } = useToastActionContext(); - - const { mutate } = useRecipeFavoriteMutation(Number(recipeId)); - - const handleToggleFavorite = async () => { - mutate( - { favorite: !isFavorite }, - { - onSuccess: () => { - setIsFavorite((prev) => !prev); - setCurrentFavoriteCount((prev) => (isFavorite ? prev - 1 : prev + 1)); - }, - onError: () => { - toast.error('꿀조합 좋아요를 다시 시도해주세요.'); - }, - } - ); - }; - - const [debouncedToggleFavorite] = useTimeout(handleToggleFavorite, 200); - - return ( - - - - {currentFavoriteCount} - - - ); -}; - -export default RecipeFavorite; - -const FavoriteButton = styled(Button)` - display: flex; - gap: 8px; - align-items: center; -`; diff --git a/frontend/src/components/Recipe/RecipeFavoriteButton/RecipeFavoriteButton.tsx b/frontend/src/components/Recipe/RecipeFavoriteButton/RecipeFavoriteButton.tsx new file mode 100644 index 00000000..4685676c --- /dev/null +++ b/frontend/src/components/Recipe/RecipeFavoriteButton/RecipeFavoriteButton.tsx @@ -0,0 +1,43 @@ +import { theme, Button, Text } from '@fun-eat/design-system'; +import styled from 'styled-components'; + +import { SvgIcon } from '@/components/Common'; +import { useTimeout } from '@/hooks/common'; +import { useRecipeFavoriteMutation } from '@/hooks/queries/recipe'; + +interface RecipeFavoriteProps { + favorite: boolean; + favoriteCount: number; + recipeId: number; +} + +const RecipeFavoriteButton = ({ recipeId, favorite, favoriteCount }: RecipeFavoriteProps) => { + const { mutate } = useRecipeFavoriteMutation(Number(recipeId)); + + const handleToggleFavorite = async () => { + mutate({ favorite: !favorite }); + }; + + const [debouncedToggleFavorite] = useTimeout(handleToggleFavorite, 200); + + return ( + + + + {favoriteCount} + + + ); +}; + +export default RecipeFavoriteButton; + +const FavoriteButton = styled(Button)` + display: flex; + gap: 8px; + align-items: center; +`; diff --git a/frontend/src/components/Recipe/index.ts b/frontend/src/components/Recipe/index.ts index b7976120..ff9566ec 100644 --- a/frontend/src/components/Recipe/index.ts +++ b/frontend/src/components/Recipe/index.ts @@ -4,7 +4,7 @@ export { default as RecipeUsedProducts } from './RecipeUsedProducts/RecipeUsedPr export { default as RecipeItem } from './RecipeItem/RecipeItem'; export { default as RecipeList } from './RecipeList/RecipeList'; export { default as RecipeRegisterForm } from './RecipeRegisterForm/RecipeRegisterForm'; -export { default as RecipeFavorite } from './RecipeFavorite/RecipeFavorite'; +export { default as RecipeFavoriteButton } from './RecipeFavoriteButton/RecipeFavoriteButton'; export { default as CommentItem } from './CommentItem/CommentItem'; export { default as CommentForm } from './CommentForm/CommentForm'; export { default as CommentList } from './CommentList/CommentList'; diff --git a/frontend/src/components/Review/ReviewFavoriteButton/ReviewFavoriteButton.tsx b/frontend/src/components/Review/ReviewFavoriteButton/ReviewFavoriteButton.tsx new file mode 100644 index 00000000..12887249 --- /dev/null +++ b/frontend/src/components/Review/ReviewFavoriteButton/ReviewFavoriteButton.tsx @@ -0,0 +1,69 @@ +import { Text, Button, useTheme } from '@fun-eat/design-system'; +import { useState } from 'react'; +import styled from 'styled-components'; + +import { SvgIcon } from '@/components/Common'; +import { useTimeout } from '@/hooks/common'; +import { useReviewFavoriteMutation } from '@/hooks/queries/review'; + +interface ReviewFavoriteButtonProps { + productId: number; + reviewId: number; + favorite: boolean; + favoriteCount: number; +} + +const ReviewFavoriteButton = ({ productId, reviewId, favorite, favoriteCount }: ReviewFavoriteButtonProps) => { + const theme = useTheme(); + + const initialFavoriteState = { + isFavorite: favorite, + currentFavoriteCount: favoriteCount, + }; + + const [favoriteInfo, setFavoriteInfo] = useState(initialFavoriteState); + const { isFavorite, currentFavoriteCount } = favoriteInfo; + + const { mutate } = useReviewFavoriteMutation(productId, reviewId); + + const handleToggleFavorite = async () => { + setFavoriteInfo((prev) => ({ + isFavorite: !prev.isFavorite, + currentFavoriteCount: isFavorite ? prev.currentFavoriteCount - 1 : prev.currentFavoriteCount + 1, + })); + + mutate( + { favorite: !isFavorite }, + { + onError: () => { + setFavoriteInfo(initialFavoriteState); + }, + } + ); + }; + + const [debouncedToggleFavorite] = useTimeout(handleToggleFavorite, 200); + + return ( + + + + {currentFavoriteCount} + + + ); +}; + +export default ReviewFavoriteButton; + +const FavoriteButton = styled(Button)` + display: flex; + align-items: center; + padding: 0; + column-gap: 8px; +`; diff --git a/frontend/src/components/Review/ReviewItem/ReviewItem.tsx b/frontend/src/components/Review/ReviewItem/ReviewItem.tsx index 14cd83d1..bbd38647 100644 --- a/frontend/src/components/Review/ReviewItem/ReviewItem.tsx +++ b/frontend/src/components/Review/ReviewItem/ReviewItem.tsx @@ -1,11 +1,10 @@ -import { Badge, Button, Text, useTheme } from '@fun-eat/design-system'; -import { memo, useState } from 'react'; +import { Badge, Text, useTheme } from '@fun-eat/design-system'; +import { memo } from 'react'; import styled from 'styled-components'; +import ReviewFavoriteButton from '../ReviewFavoriteButton/ReviewFavoriteButton'; + import { SvgIcon, TagList } from '@/components/Common'; -import { useTimeout } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; -import { useReviewFavoriteMutation } from '@/hooks/queries/review'; import type { Review } from '@/types/review'; import { getRelativeDate } from '@/utils/date'; @@ -15,37 +14,10 @@ interface ReviewItemProps { } const ReviewItem = ({ productId, review }: ReviewItemProps) => { - const { id, userName, profileImage, image, rating, tags, content, createdAt, rebuy, favoriteCount, favorite } = - review; - const [isFavorite, setIsFavorite] = useState(favorite); - const [currentFavoriteCount, setCurrentFavoriteCount] = useState(favoriteCount); - - const { toast } = useToastActionContext(); - const { mutate } = useReviewFavoriteMutation(productId, id); - const theme = useTheme(); - const handleToggleFavorite = async () => { - mutate( - { favorite: !isFavorite }, - { - onSuccess: () => { - setIsFavorite((prev) => !prev); - setCurrentFavoriteCount((prev) => (isFavorite ? prev - 1 : prev + 1)); - }, - onError: (error) => { - if (error instanceof Error) { - toast.error(error.message); - return; - } - - toast.error('리뷰 좋아요를 다시 시도해주세요.'); - }, - } - ); - }; - - const [debouncedToggleFavorite] = useTimeout(handleToggleFavorite, 200); + const { id, userName, profileImage, image, rating, tags, content, createdAt, rebuy, favorite, favoriteCount } = + review; return ( @@ -79,17 +51,7 @@ const ReviewItem = ({ productId, review }: ReviewItemProps) => { {image && } {content} - - - - {currentFavoriteCount} - - + ); }; @@ -141,10 +103,3 @@ const ReviewImage = styled.img` const ReviewContent = styled(Text)` white-space: pre-wrap; `; - -const FavoriteButton = styled(Button)` - display: flex; - align-items: center; - padding: 0; - column-gap: 8px; -`; diff --git a/frontend/src/components/Review/index.ts b/frontend/src/components/Review/index.ts index ac1fa9cd..399ee5e3 100644 --- a/frontend/src/components/Review/index.ts +++ b/frontend/src/components/Review/index.ts @@ -4,3 +4,4 @@ export { default as ReviewTagItem } from './ReviewTagItem/ReviewTagItem'; export { default as ReviewTagList } from './ReviewTagList/ReviewTagList'; export { default as ReviewRegisterForm } from './ReviewRegisterForm/ReviewRegisterForm'; export { default as BestReviewItem } from './BestReviewItem/BestReviewItem'; +export { default as ReviewFavoriteButton } from './ReviewFavoriteButton/ReviewFavoriteButton'; diff --git a/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts b/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts index c97b392a..ae015303 100644 --- a/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts +++ b/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts @@ -1,7 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { recipeApi } from '@/apis'; -import type { RecipeFavoriteRequestBody } from '@/types/recipe'; +import { useToastActionContext } from '@/hooks/context'; +import type { RecipeFavoriteRequestBody, RecipeDetail } from '@/types/recipe'; const headers = { 'Content-Type': 'application/json' }; @@ -11,10 +12,42 @@ const patchRecipeFavorite = (recipeId: number, body: RecipeFavoriteRequestBody) const useRecipeFavoriteMutation = (recipeId: number) => { const queryClient = useQueryClient(); + const { toast } = useToastActionContext(); + + const queryKey = ['recipeDetail', recipeId]; return useMutation({ mutationFn: (body: RecipeFavoriteRequestBody) => patchRecipeFavorite(recipeId, body), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['recipeDetail', recipeId] }), + onMutate: async (newFavoriteRequest) => { + await queryClient.cancelQueries({ queryKey: queryKey }); + + const previousRequest = queryClient.getQueryData(queryKey); + + if (previousRequest) { + queryClient.setQueryData(queryKey, () => ({ + ...previousRequest, + favorite: newFavoriteRequest.favorite, + favoriteCount: newFavoriteRequest.favorite + ? previousRequest.favoriteCount + 1 + : previousRequest.favoriteCount - 1, + })); + } + + return { previousRequest }; + }, + onError: (error, _, context) => { + queryClient.setQueryData(queryKey, context?.previousRequest); + + if (error instanceof Error) { + toast.error(error.message); + return; + } + + toast.error('좋아요를 다시 시도해주세요.'); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: queryKey }); + }, }); }; diff --git a/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts b/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts index 0d35ab61..6cdd51bc 100644 --- a/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts +++ b/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts @@ -1,6 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { productApi } from '@/apis'; +import { useToastActionContext } from '@/hooks/context'; import type { ReviewFavoriteRequestBody } from '@/types/review'; const headers = { 'Content-Type': 'application/json' }; @@ -11,10 +12,21 @@ const patchReviewFavorite = (productId: number, reviewId: number, body: ReviewFa const useReviewFavoriteMutation = (productId: number, reviewId: number) => { const queryClient = useQueryClient(); + const { toast } = useToastActionContext(); + + const queryKey = ['product', productId, 'review']; return useMutation({ mutationFn: (body: ReviewFavoriteRequestBody) => patchReviewFavorite(productId, reviewId, body), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product', productId, 'review'] }), + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + return; + } + + toast.error('좋아요를 다시 시도해주세요.'); + }, + onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKey }), }); }; diff --git a/frontend/src/pages/RecipeDetailPage.tsx b/frontend/src/pages/RecipeDetailPage.tsx index 1f98f5d9..4b4fbb66 100644 --- a/frontend/src/pages/RecipeDetailPage.tsx +++ b/frontend/src/pages/RecipeDetailPage.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components'; import RecipePreviewImage from '@/assets/plate.svg'; import { ErrorBoundary, ErrorComponent, Loading, SectionTitle } from '@/components/Common'; -import { CommentForm, CommentList, RecipeFavorite } from '@/components/Recipe'; +import { CommentForm, CommentList, RecipeFavoriteButton } from '@/components/Recipe'; import { useRecipeDetailQuery } from '@/hooks/queries/recipe'; import { getFormattedDate } from '@/utils/date'; @@ -46,7 +46,7 @@ export const RecipeDetailPage = () => { {getFormattedDate(createdAt)} - + From 70f34f143e76c67625d69467a120f0e66dd4e009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=E1=B4=8F=CA=9F=CA=99=C9=AA=20=E2=98=94=EF=B8=8F?= Date: Sun, 19 Nov 2023 22:07:35 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[FE]=20refactor:=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 디자인 시스템 버전 업 * refactor: 캐러셀 교체 * refactor: input 교체 * refactor: 토스트 교체 --- frontend/package.json | 2 +- .../Common/Carousel/Carousel.stories.tsx | 52 ---------- .../components/Common/Carousel/Carousel.tsx | 63 ------------ .../Common/ImageUploader/ImageUploader.tsx | 3 +- .../components/Common/Input/Input.stories.tsx | 50 ---------- .../src/components/Common/Input/Input.tsx | 97 ------------------- .../Common/Skeleton/Skeleton.stories.tsx | 18 ---- .../components/Common/Skeleton/Skeleton.tsx | 36 ------- .../components/Common/Toast/Toast.stories.tsx | 49 ---------- .../src/components/Common/Toast/Toast.tsx | 41 -------- frontend/src/components/Common/index.ts | 4 - .../MemberModifyInput/MemberModifyInput.tsx | 4 +- .../MemberReviewItem.stories.tsx | 9 -- .../MemberReviewItem/MemberReviewItem.tsx | 3 +- .../Product/ProductItem/ProductItem.tsx | 4 +- .../RecipeRankingItem/RecipeRankingItem.tsx | 4 +- .../RecipeRankingList/RecipeRankingList.tsx | 3 +- .../Recipe/CommentForm/CommentForm.tsx | 3 +- .../Recipe/RecipeItem/RecipeItem.tsx | 4 +- .../RecipeNameInput/RecipeNameInput.tsx | 3 +- .../RecipeRegisterForm/RecipeRegisterForm.tsx | 4 +- .../RecipeUsedProducts/RecipeUsedProducts.tsx | 4 +- .../ReviewRegisterForm/ReviewRegisterForm.tsx | 4 +- frontend/src/contexts/ToastContext.tsx | 80 --------------- frontend/src/hooks/common/index.ts | 1 - frontend/src/hooks/common/useImageUploader.ts | 3 +- frontend/src/hooks/common/useToast.ts | 37 ------- frontend/src/hooks/context/index.ts | 2 - .../hooks/context/useToastActionContext.ts | 14 --- .../src/hooks/context/useToastValueContext.ts | 14 --- .../queries/members/useLogoutMutation.ts | 2 +- frontend/src/hooks/search/useSearch.ts | 2 +- frontend/src/index.tsx | 5 +- frontend/src/pages/IntegratedSearchPage.tsx | 4 +- frontend/src/pages/MemberModifyPage.tsx | 3 +- frontend/src/pages/SearchPage.tsx | 4 +- frontend/yarn.lock | 8 +- 37 files changed, 32 insertions(+), 611 deletions(-) delete mode 100644 frontend/src/components/Common/Carousel/Carousel.stories.tsx delete mode 100644 frontend/src/components/Common/Carousel/Carousel.tsx delete mode 100644 frontend/src/components/Common/Input/Input.stories.tsx delete mode 100644 frontend/src/components/Common/Input/Input.tsx delete mode 100644 frontend/src/components/Common/Skeleton/Skeleton.stories.tsx delete mode 100644 frontend/src/components/Common/Skeleton/Skeleton.tsx delete mode 100644 frontend/src/components/Common/Toast/Toast.stories.tsx delete mode 100644 frontend/src/components/Common/Toast/Toast.tsx delete mode 100644 frontend/src/contexts/ToastContext.tsx delete mode 100644 frontend/src/hooks/common/useToast.ts delete mode 100644 frontend/src/hooks/context/useToastActionContext.ts delete mode 100644 frontend/src/hooks/context/useToastValueContext.ts diff --git a/frontend/package.json b/frontend/package.json index 358f1fe4..33515820 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,7 @@ "test:coverage": "jest --watchAll --coverage" }, "dependencies": { - "@fun-eat/design-system": "^0.3.18", + "@fun-eat/design-system": "^0.4.1", "@tanstack/react-query": "^4.32.6", "@tanstack/react-query-devtools": "^4.32.6", "browser-image-compression": "^2.0.2", diff --git a/frontend/src/components/Common/Carousel/Carousel.stories.tsx b/frontend/src/components/Common/Carousel/Carousel.stories.tsx deleted file mode 100644 index 8d7eccf6..00000000 --- a/frontend/src/components/Common/Carousel/Carousel.stories.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Carousel from './Carousel'; - -import { RecipeItem } from '@/components/Recipe'; -import mockRecipe from '@/mocks/data/recipes.json'; - -const meta: Meta = { - title: 'common/Carousel', - component: Carousel, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - carouselList: [ - { - id: 0, - children:
1
, - }, - { - id: 1, - children:
2
, - }, - { - id: 2, - children:
3
, - }, - ], - }, -}; - -export const RecipeRanking: Story = { - args: { - carouselList: [ - { - id: 0, - children: , - }, - { - id: 1, - children: , - }, - { - id: 2, - children: , - }, - ], - }, -}; diff --git a/frontend/src/components/Common/Carousel/Carousel.tsx b/frontend/src/components/Common/Carousel/Carousel.tsx deleted file mode 100644 index 038b5cef..00000000 --- a/frontend/src/components/Common/Carousel/Carousel.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; - -import type { CarouselChildren } from '@/types/common'; - -interface CarouselProps { - carouselList: CarouselChildren[]; -} - -const Carousel = ({ carouselList }: CarouselProps) => { - const extendedCarouselList = [...carouselList, carouselList[0]]; - const [currentIndex, setCurrentIndex] = useState(0); - - const CAROUSEL_WIDTH = window.innerWidth; - - const showNextSlide = () => { - setCurrentIndex((prev) => (prev === carouselList.length ? 0 : prev + 1)); - }; - - useEffect(() => { - const timer = setInterval(showNextSlide, 2000); - - return () => clearInterval(timer); - }, [currentIndex]); - - return ( - - - {extendedCarouselList.map(({ id, children }, index) => ( - - {children} - - ))} - - - ); -}; - -export default Carousel; - -const CarouselContainer = styled.div` - display: flex; - width: 100%; - border: 1px solid ${({ theme }) => theme.colors.gray2}; - border-radius: 10px; - overflow: hidden; -`; - -const CarouselWrapper = styled.ul` - display: flex; -`; - -const CarouselItem = styled.li` - height: fit-content; -`; diff --git a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx b/frontend/src/components/Common/ImageUploader/ImageUploader.tsx index 9c915081..b139a1b4 100644 --- a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx +++ b/frontend/src/components/Common/ImageUploader/ImageUploader.tsx @@ -1,10 +1,9 @@ -import { Button } from '@fun-eat/design-system'; +import { Button, useToastActionContext } from '@fun-eat/design-system'; import type { ChangeEventHandler } from 'react'; import styled from 'styled-components'; import { IMAGE_MAX_SIZE } from '@/constants'; import { useEnterKeyDown } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; interface ReviewImageUploaderProps { previewImage: string; diff --git a/frontend/src/components/Common/Input/Input.stories.tsx b/frontend/src/components/Common/Input/Input.stories.tsx deleted file mode 100644 index 48e8856a..00000000 --- a/frontend/src/components/Common/Input/Input.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Input from './Input'; -import SvgIcon from '../Svg/SvgIcon'; - -const meta: Meta = { - title: 'common/Input', - component: Input, - argTypes: { - rightIcon: { - control: { type: 'boolean' }, - mapping: { false: '', true: }, - }, - }, - args: { - customWidth: '300px', - isError: false, - rightIcon: false, - errorMessage: '', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const WithPlaceholder: Story = { - args: { - placeholder: '상품 이름을 검색하세요.', - }, -}; - -export const WithIcon: Story = { - args: { - placeholder: '상품 이름을 검색하세요.', - rightIcon: true, - }, -}; - -export const Error: Story = { - args: { - isError: true, - errorMessage: '10글자 이내로 입력해주세요.', - }, -}; - -export const Disabled: Story = { - render: () => , -}; diff --git a/frontend/src/components/Common/Input/Input.tsx b/frontend/src/components/Common/Input/Input.tsx deleted file mode 100644 index c3b3b40f..00000000 --- a/frontend/src/components/Common/Input/Input.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Text, theme } from '@fun-eat/design-system'; -import type { ComponentPropsWithRef, ForwardedRef, ReactNode } from 'react'; -import { forwardRef } from 'react'; -import styled from 'styled-components'; - -interface InputProps extends ComponentPropsWithRef<'input'> { - /** - * Input 컴포넌트의 너비값입니다. - */ - customWidth?: string; - /** - * Input 컴포넌트의 최소 너비값입니다. - */ - minWidth?: string; - /** - * Input value에 에러가 있는지 여부입니다. - */ - isError?: boolean; - /** - * Input 컴포넌트 오른쪽에 위치할 아이콘입니다. - */ - rightIcon?: ReactNode; - /** - * isError가 true일 때 보여줄 에러 메시지입니다. - */ - errorMessage?: string; -} - -const Input = forwardRef( - ( - { customWidth = '300px', minWidth, isError = false, rightIcon, errorMessage, ...props }: InputProps, - ref: ForwardedRef - ) => { - return ( - <> - - - {rightIcon && {rightIcon}} - - {isError && {errorMessage}} - - ); - } -); - -Input.displayName = 'Input'; - -export default Input; - -type InputContainerStyleProps = Pick; -type CustomInputStyleProps = Pick; - -const InputContainer = styled.div` - position: relative; - min-width: ${({ minWidth }) => minWidth ?? 0}; - max-width: ${({ customWidth }) => customWidth}; - text-align: center; -`; - -const CustomInput = styled.input` - width: 100%; - height: 40px; - padding: 10px 0 10px 12px; - color: ${({ isError }) => (isError ? theme.colors.error : theme.textColors.default)}; - border: 1px solid ${({ isError }) => (isError ? theme.colors.error : theme.borderColors.default)}; - border-radius: 5px; - - &:focus { - border: 2px solid ${({ isError }) => (isError ? theme.colors.error : theme.borderColors.strong)}; - outline: none; - } - - &:disabled { - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - background: ${({ theme }) => theme.colors.gray1}; - } - - &::placeholder { - color: ${theme.textColors.disabled}; - font-size: ${theme.fontSizes.sm}; - } -`; - -const IconWrapper = styled.div` - position: absolute; - top: 0; - right: 0; - display: flex; - align-items: center; - height: 100%; - margin-right: 8px; -`; - -const ErrorMessage = styled(Text)` - color: ${theme.colors.error}; - font-size: ${theme.fontSizes.xs}; -`; diff --git a/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx b/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx deleted file mode 100644 index e8952c31..00000000 --- a/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Skeleton from './Skeleton'; - -const meta: Meta = { - title: 'common/Skeleton', - component: Skeleton, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - width: 100, - height: 100, - }, -}; diff --git a/frontend/src/components/Common/Skeleton/Skeleton.tsx b/frontend/src/components/Common/Skeleton/Skeleton.tsx deleted file mode 100644 index 857d0307..00000000 --- a/frontend/src/components/Common/Skeleton/Skeleton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react'; -import styled from 'styled-components'; - -interface SkeletonProps extends ComponentPropsWithoutRef<'div'> { - width?: string | number; - height?: string | number; -} - -const Skeleton = ({ width, height }: SkeletonProps) => { - return ; -}; - -export default Skeleton; - -export const SkeletonContainer = styled.div` - position: absolute; - width: ${({ width }) => (typeof width === 'number' ? width + 'px' : width)}; - height: ${({ height }) => (typeof height === 'number' ? height + 'px' : height)}; - border-radius: 8px; - background: linear-gradient(-90deg, #dddddd, #f7f7f7, #dddddd, #f7f7f7); - background-size: 400%; - overflow: hidden; - animation: skeleton-gradient 5s infinite ease-out; - - @keyframes skeleton-gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } - } -`; diff --git a/frontend/src/components/Common/Toast/Toast.stories.tsx b/frontend/src/components/Common/Toast/Toast.stories.tsx deleted file mode 100644 index 383c4375..00000000 --- a/frontend/src/components/Common/Toast/Toast.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Toast from './Toast'; - -import ToastProvider from '@/contexts/ToastContext'; -import { useToastActionContext } from '@/hooks/context'; - -const meta: Meta = { - title: 'common/Toast', - component: Toast, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => { - const { toast } = useToastActionContext(); - const handleClick = () => { - toast.success('성공'); - }; - return ( -
- -
- ); - }, -}; - -export const Error: Story = { - render: () => { - const { toast } = useToastActionContext(); - const handleClick = () => { - toast.error('실패'); - }; - return ( -
- -
- ); - }, -}; diff --git a/frontend/src/components/Common/Toast/Toast.tsx b/frontend/src/components/Common/Toast/Toast.tsx deleted file mode 100644 index c9d9dc46..00000000 --- a/frontend/src/components/Common/Toast/Toast.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useToast } from '@/hooks/common'; -import { fadeOut, slideIn } from '@/styles/animations'; - -interface ToastProps { - id: number; - message: string; - isError?: boolean; -} - -const Toast = ({ id, message, isError = false }: ToastProps) => { - const theme = useTheme(); - const isShown = useToast(id); - - return ( - - {message} - - ); -}; - -export default Toast; - -type ToastStyleProps = Pick & { isAnimating?: boolean }; - -const ToastWrapper = styled.div` - position: relative; - width: calc(100% - 20px); - height: 55px; - max-width: 560px; - border-radius: 10px; - background: ${({ isError, theme }) => (isError ? theme.colors.error : theme.colors.black)}; - animation: ${({ isAnimating }) => (isAnimating ? slideIn : fadeOut)} 0.3s ease-in-out forwards; -`; - -const Message = styled(Text)` - margin-left: 20px; - line-height: 55px; -`; diff --git a/frontend/src/components/Common/index.ts b/frontend/src/components/Common/index.ts index 070263f5..f5b21821 100644 --- a/frontend/src/components/Common/index.ts +++ b/frontend/src/components/Common/index.ts @@ -10,18 +10,14 @@ export { default as TabMenu } from './TabMenu/TabMenu'; export { default as TagList } from './TagList/TagList'; export { default as SectionTitle } from './SectionTitle/SectionTitle'; export { default as ScrollButton } from './ScrollButton/ScrollButton'; -export { default as Input } from './Input/Input'; export { default as ImageUploader } from './ImageUploader/ImageUploader'; export { default as ErrorBoundary } from './ErrorBoundary/ErrorBoundary'; export { default as ErrorComponent } from './ErrorComponent/ErrorComponent'; export { default as Loading } from './Loading/Loading'; export { default as MarkedText } from './MarkedText/MarkedText'; export { default as NavigableSectionTitle } from './NavigableSectionTitle/NavigableSectionTitle'; -export { default as Carousel } from './Carousel/Carousel'; export { default as RegisterButton } from './RegisterButton/RegisterButton'; -export { default as Toast } from './Toast/Toast'; export { default as CategoryItem } from './CategoryItem/CategoryItem'; export { default as CategoryFoodList } from './CategoryFoodList/CategoryFoodList'; export { default as CategoryStoreList } from './CategoryStoreList/CategoryStoreList'; -export { default as Skeleton } from './Skeleton/Skeleton'; export { default as Banner } from './Banner/Banner'; diff --git a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx b/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx index 39a78e6c..6a03e450 100644 --- a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx +++ b/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx @@ -1,9 +1,7 @@ -import { Heading, Spacing, Text, useTheme } from '@fun-eat/design-system'; +import { Heading, Spacing, Text, Input, useTheme } from '@fun-eat/design-system'; import type { ChangeEventHandler } from 'react'; import styled from 'styled-components'; -import { Input } from '@/components/Common'; - const MIN_LENGTH = 1; const MAX_LENGTH = 10; diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx index a631341d..3856648f 100644 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx +++ b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx @@ -2,18 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react'; import MemberReviewItem from './MemberReviewItem'; -import ToastProvider from '@/contexts/ToastContext'; - const meta: Meta = { title: 'members/MemberReviewItem', component: MemberReviewItem, - decorators: [ - (Story) => ( - - - - ), - ], args: { review: { reviewId: 1, diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx index 1d450385..3024b982 100644 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx +++ b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx @@ -1,9 +1,8 @@ -import { useTheme, Spacing, Text, Button } from '@fun-eat/design-system'; +import { useTheme, Spacing, Text, Button, useToastActionContext } from '@fun-eat/design-system'; import type { MouseEventHandler } from 'react'; import styled from 'styled-components'; import { SvgIcon } from '@/components/Common'; -import { useToastActionContext } from '@/hooks/context'; import { useDeleteReview } from '@/hooks/queries/members'; import type { MemberReview } from '@/types/review'; diff --git a/frontend/src/components/Product/ProductItem/ProductItem.tsx b/frontend/src/components/Product/ProductItem/ProductItem.tsx index 43dc773e..d961894d 100644 --- a/frontend/src/components/Product/ProductItem/ProductItem.tsx +++ b/frontend/src/components/Product/ProductItem/ProductItem.tsx @@ -1,11 +1,11 @@ -import { Text, useTheme } from '@fun-eat/design-system'; +import { Text, Skeleton, useTheme } from '@fun-eat/design-system'; import { memo, useState } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; import PreviewImage from '@/assets/characters.svg'; import PBPreviewImage from '@/assets/samgakgimbab.svg'; -import { Skeleton, SvgIcon } from '@/components/Common'; +import { SvgIcon } from '@/components/Common'; import { CATEGORY_TYPE } from '@/constants'; import type { Product } from '@/types/product'; diff --git a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx index b887ad10..308f2d3f 100644 --- a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx +++ b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx @@ -1,9 +1,9 @@ -import { Spacing, Text, useTheme } from '@fun-eat/design-system'; +import { Spacing, Text, Skeleton, useTheme } from '@fun-eat/design-system'; import { useState } from 'react'; import styled from 'styled-components'; import RecipePreviewImage from '@/assets/plate.svg'; -import { Skeleton, SvgIcon } from '@/components/Common'; +import { SvgIcon } from '@/components/Common'; import type { RecipeRanking } from '@/types/ranking'; import { getRelativeDate } from '@/utils/date'; diff --git a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx index 76397964..368bff09 100644 --- a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx +++ b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx @@ -1,9 +1,8 @@ -import { Link, Text } from '@fun-eat/design-system'; +import { Carousel, Link, Text } from '@fun-eat/design-system'; import { Link as RouterLink } from 'react-router-dom'; import RecipeRankingItem from '../RecipeRankingItem/RecipeRankingItem'; -import { Carousel } from '@/components/Common'; import { PATH } from '@/constants/path'; import { useGA } from '@/hooks/common'; import { useRecipeRankingQuery } from '@/hooks/queries/rank'; diff --git a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx b/frontend/src/components/Recipe/CommentForm/CommentForm.tsx index 10465755..5eb0ac1c 100644 --- a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx +++ b/frontend/src/components/Recipe/CommentForm/CommentForm.tsx @@ -1,11 +1,10 @@ -import { Button, Spacing, Text, Textarea, useTheme } from '@fun-eat/design-system'; +import { Button, Spacing, Text, Textarea, useTheme, useToastActionContext } from '@fun-eat/design-system'; import type { ChangeEventHandler, FormEventHandler, RefObject } from 'react'; import { useState } from 'react'; import styled from 'styled-components'; import { SvgIcon } from '@/components/Common'; import { useScroll } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; import { useRecipeCommentMutation } from '@/hooks/queries/recipe'; interface CommentFormProps { diff --git a/frontend/src/components/Recipe/RecipeItem/RecipeItem.tsx b/frontend/src/components/Recipe/RecipeItem/RecipeItem.tsx index 5846bd87..f7e7f229 100644 --- a/frontend/src/components/Recipe/RecipeItem/RecipeItem.tsx +++ b/frontend/src/components/Recipe/RecipeItem/RecipeItem.tsx @@ -1,9 +1,9 @@ -import { Heading, Text, useTheme } from '@fun-eat/design-system'; +import { Heading, Text, Skeleton, useTheme } from '@fun-eat/design-system'; import { Fragment, memo, useState } from 'react'; import styled from 'styled-components'; import PreviewImage from '@/assets/plate.svg'; -import { Skeleton, SvgIcon } from '@/components/Common'; +import { SvgIcon } from '@/components/Common'; import type { MemberRecipe, Recipe } from '@/types/recipe'; import { getFormattedDate } from '@/utils/date'; diff --git a/frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.tsx b/frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.tsx index 3ab0d8bc..093f3114 100644 --- a/frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.tsx +++ b/frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.tsx @@ -1,8 +1,7 @@ -import { Heading, Spacing, Text, useTheme } from '@fun-eat/design-system'; +import { Heading, Input, Spacing, Text, useTheme } from '@fun-eat/design-system'; import type { ChangeEventHandler } from 'react'; import styled from 'styled-components'; -import { Input } from '@/components/Common'; import { useRecipeFormActionContext } from '@/hooks/context'; const MIN_LENGTH = 1; diff --git a/frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.tsx b/frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.tsx index e92d894c..dbc7f525 100644 --- a/frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.tsx +++ b/frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, Heading, Spacing, Text, useTheme } from '@fun-eat/design-system'; +import { Button, Divider, Heading, Spacing, Text, useTheme, useToastActionContext } from '@fun-eat/design-system'; import type { FormEventHandler } from 'react'; import styled from 'styled-components'; @@ -8,7 +8,7 @@ import RecipeUsedProducts from '../RecipeUsedProducts/RecipeUsedProducts'; import { ImageUploader, SvgIcon } from '@/components/Common'; import { useImageUploader, useFormData } from '@/hooks/common'; -import { useRecipeFormValueContext, useRecipeFormActionContext, useToastActionContext } from '@/hooks/context'; +import { useRecipeFormValueContext, useRecipeFormActionContext } from '@/hooks/context'; import { useRecipeRegisterFormMutation } from '@/hooks/queries/recipe'; import type { RecipeRequest } from '@/types/recipe'; diff --git a/frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.tsx b/frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.tsx index 8ee6faec..00739f3f 100644 --- a/frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.tsx +++ b/frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.tsx @@ -1,11 +1,11 @@ -import { Badge, Button, Heading, Text, useTheme } from '@fun-eat/design-system'; +import { Badge, Button, Heading, Text, Input, useTheme } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; import { Suspense, useState } from 'react'; import styled from 'styled-components'; import SearchedProductList from './SearchedProductList'; -import { ErrorBoundary, ErrorComponent, Input, Loading, SvgIcon } from '@/components/Common'; +import { ErrorBoundary, ErrorComponent, Loading, SvgIcon } from '@/components/Common'; import { useDebounce } from '@/hooks/common'; import { useRecipeFormActionContext } from '@/hooks/context'; import { useSearch } from '@/hooks/search'; diff --git a/frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.tsx b/frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.tsx index 6c422c68..7c3ac6ec 100644 --- a/frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.tsx +++ b/frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, Heading, Spacing, Text, theme } from '@fun-eat/design-system'; +import { Button, Divider, Heading, Spacing, Text, theme, useToastActionContext } from '@fun-eat/design-system'; import type { FormEventHandler, RefObject } from 'react'; import styled from 'styled-components'; @@ -11,7 +11,7 @@ import { ImageUploader, SvgIcon } from '@/components/Common'; import { ProductOverviewItem } from '@/components/Product'; import { MIN_DISPLAYED_TAGS_LENGTH } from '@/constants'; import { useFormData, useImageUploader, useScroll } from '@/hooks/common'; -import { useReviewFormActionContext, useReviewFormValueContext, useToastActionContext } from '@/hooks/context'; +import { useReviewFormActionContext, useReviewFormValueContext } from '@/hooks/context'; import { useProductDetailQuery } from '@/hooks/queries/product'; import { useReviewRegisterFormMutation } from '@/hooks/queries/review'; import type { ReviewRequest } from '@/types/review'; diff --git a/frontend/src/contexts/ToastContext.tsx b/frontend/src/contexts/ToastContext.tsx deleted file mode 100644 index f1414864..00000000 --- a/frontend/src/contexts/ToastContext.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import { createContext, useState } from 'react'; -import { createPortal } from 'react-dom'; -import styled from 'styled-components'; - -import { Toast } from '@/components/Common'; - -interface ToastState { - id: number; - message: string; - isError?: boolean; -} - -interface ToastValue { - toasts: ToastState[]; -} -interface ToastAction { - toast: { - success: (message: string) => void; - error: (message: string) => void; - }; - deleteToast: (id: number) => void; -} - -export const ToastValueContext = createContext(null); -export const ToastActionContext = createContext(null); - -const ToastProvider = ({ children }: PropsWithChildren) => { - const [toasts, setToasts] = useState([]); - - const showToast = (id: number, message: string, isError?: boolean) => { - setToasts([...toasts, { id, message, isError }]); - }; - - const deleteToast = (id: number) => { - setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id)); - }; - - const toast = { - success: (message: string) => showToast(Number(Date.now()), message), - error: (message: string) => showToast(Number(Date.now()), message, true), - }; - - const toastValue = { - toasts, - }; - - const toastAction = { - toast, - deleteToast, - }; - - return ( - - - {children} - {createPortal( - - {toasts.map(({ id, message, isError }) => ( - - ))} - , - document.getElementById('toast-container') as HTMLElement - )} - - - ); -}; - -export default ToastProvider; - -const ToastContainer = styled.div` - position: fixed; - z-index: 1000; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - transform: translate(0, -10px); -`; diff --git a/frontend/src/hooks/common/index.ts b/frontend/src/hooks/common/index.ts index 199ae608..2cbf28c1 100644 --- a/frontend/src/hooks/common/index.ts +++ b/frontend/src/hooks/common/index.ts @@ -10,5 +10,4 @@ export { default as useTimeout } from './useTimeout'; export { default as useRouteChangeTracker } from './useRouteChangeTracker'; export { default as useTabMenu } from './useTabMenu'; export { default as useScrollRestoration } from './useScrollRestoration'; -export { default as useToast } from './useToast'; export { default as useGA } from './useGA'; diff --git a/frontend/src/hooks/common/useImageUploader.ts b/frontend/src/hooks/common/useImageUploader.ts index cc093517..1f56a992 100644 --- a/frontend/src/hooks/common/useImageUploader.ts +++ b/frontend/src/hooks/common/useImageUploader.ts @@ -1,8 +1,7 @@ +import { useToastActionContext } from '@fun-eat/design-system'; import imageCompression from 'browser-image-compression'; import { useState } from 'react'; -import { useToastActionContext } from '../context'; - const isImageFile = (file: File) => file.type !== 'image/png' && file.type !== 'image/jpeg'; const options = { diff --git a/frontend/src/hooks/common/useToast.ts b/frontend/src/hooks/common/useToast.ts deleted file mode 100644 index f95f33ef..00000000 --- a/frontend/src/hooks/common/useToast.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -import { useToastActionContext } from '../context'; - -const useToast = (id: number) => { - const { deleteToast } = useToastActionContext(); - const [isShown, setIsShown] = useState(true); - - const showTimeoutRef = useRef(null); - const deleteTimeoutRef = useRef(null); - - useEffect(() => { - showTimeoutRef.current = window.setTimeout(() => setIsShown(false), 2000); - - return () => { - if (showTimeoutRef.current) { - clearTimeout(showTimeoutRef.current); - } - }; - }, []); - - useEffect(() => { - if (!isShown) { - deleteTimeoutRef.current = window.setTimeout(() => deleteToast(id), 2000); - } - - return () => { - if (deleteTimeoutRef.current) { - clearTimeout(deleteTimeoutRef.current); - } - }; - }, [isShown]); - - return isShown; -}; - -export default useToast; diff --git a/frontend/src/hooks/context/index.ts b/frontend/src/hooks/context/index.ts index dd03253c..56470cfb 100644 --- a/frontend/src/hooks/context/index.ts +++ b/frontend/src/hooks/context/index.ts @@ -4,5 +4,3 @@ export { default as useReviewFormActionContext } from './useReviewFormActionCont export { default as useReviewFormValueContext } from './useReviewFormValueContext'; export { default as useRecipeFormActionContext } from './useRecipeFormActionContext'; export { default as useRecipeFormValueContext } from './useRecipeFormValueContext'; -export { default as useToastActionContext } from './useToastActionContext'; -export { default as useToastValueContext } from './useToastValueContext'; diff --git a/frontend/src/hooks/context/useToastActionContext.ts b/frontend/src/hooks/context/useToastActionContext.ts deleted file mode 100644 index e0d7e31a..00000000 --- a/frontend/src/hooks/context/useToastActionContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from 'react'; - -import { ToastActionContext } from '@/contexts/ToastContext'; - -const useToastActionContext = () => { - const toastAction = useContext(ToastActionContext); - if (toastAction === null || toastAction === undefined) { - throw new Error('useToastActionContext는 Toast Provider 안에서 사용해야 합니다.'); - } - - return toastAction; -}; - -export default useToastActionContext; diff --git a/frontend/src/hooks/context/useToastValueContext.ts b/frontend/src/hooks/context/useToastValueContext.ts deleted file mode 100644 index ca4b65ca..00000000 --- a/frontend/src/hooks/context/useToastValueContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from 'react'; - -import { ToastValueContext } from '@/contexts/ToastContext'; - -const useToastValueContext = () => { - const toastValue = useContext(ToastValueContext); - if (toastValue === null || toastValue === undefined) { - throw new Error('useToastValueContext는 Toast Provider 안에서 사용해야 합니다.'); - } - - return toastValue; -}; - -export default useToastValueContext; diff --git a/frontend/src/hooks/queries/members/useLogoutMutation.ts b/frontend/src/hooks/queries/members/useLogoutMutation.ts index afc27e2b..8aed8884 100644 --- a/frontend/src/hooks/queries/members/useLogoutMutation.ts +++ b/frontend/src/hooks/queries/members/useLogoutMutation.ts @@ -1,9 +1,9 @@ +import { useToastActionContext } from '@fun-eat/design-system'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { logoutApi } from '@/apis'; import { PATH } from '@/constants/path'; -import { useToastActionContext } from '@/hooks/context'; const useLogoutMutation = () => { const navigate = useNavigate(); diff --git a/frontend/src/hooks/search/useSearch.ts b/frontend/src/hooks/search/useSearch.ts index ba985394..bf045220 100644 --- a/frontend/src/hooks/search/useSearch.ts +++ b/frontend/src/hooks/search/useSearch.ts @@ -1,9 +1,9 @@ +import { useToastActionContext } from '@fun-eat/design-system'; import type { ChangeEventHandler, FormEventHandler, MouseEventHandler } from 'react'; import { useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useGA } from '../common'; -import { useToastActionContext } from '../context'; const useSearch = () => { const inputRef = useRef(null); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 7cb7bc77..0299216d 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -8,7 +8,6 @@ import { RouterProvider } from 'react-router-dom'; import { SvgSprite } from './components/Common'; import { ENVIRONMENT } from './constants'; -import ToastProvider from './contexts/ToastContext'; import router from './router'; import GlobalStyle from './styles/globalStyle'; @@ -43,9 +42,7 @@ root.render( - - ...loading

} /> -
+ ...loading

} />
diff --git a/frontend/src/pages/IntegratedSearchPage.tsx b/frontend/src/pages/IntegratedSearchPage.tsx index 77f781f3..a74f7a77 100644 --- a/frontend/src/pages/IntegratedSearchPage.tsx +++ b/frontend/src/pages/IntegratedSearchPage.tsx @@ -1,9 +1,9 @@ -import { Button, Heading, Spacing, Text } from '@fun-eat/design-system'; +import { Button, Heading, Spacing, Text, Input } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; import { Suspense, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { ErrorBoundary, ErrorComponent, Input, Loading, SvgIcon, TabMenu } from '@/components/Common'; +import { ErrorBoundary, ErrorComponent, Loading, SvgIcon, TabMenu } from '@/components/Common'; import { RecommendList, ProductSearchResultList, RecipeSearchResultList } from '@/components/Search'; import { SEARCH_TAB_VARIANTS } from '@/constants'; import { useDebounce, useTabMenu } from '@/hooks/common'; diff --git a/frontend/src/pages/MemberModifyPage.tsx b/frontend/src/pages/MemberModifyPage.tsx index 6b3031a9..4e6624e5 100644 --- a/frontend/src/pages/MemberModifyPage.tsx +++ b/frontend/src/pages/MemberModifyPage.tsx @@ -1,4 +1,4 @@ -import { Button, Spacing } from '@fun-eat/design-system'; +import { Button, Spacing, useToastActionContext } from '@fun-eat/design-system'; import type { ChangeEventHandler, FormEventHandler } from 'react'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -8,7 +8,6 @@ import { SectionTitle, SvgIcon } from '@/components/Common'; import { MemberModifyInput } from '@/components/Members'; import { IMAGE_MAX_SIZE } from '@/constants'; import { useFormData, useImageUploader } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; import { useMemberModifyMutation, useMemberQuery } from '@/hooks/queries/members'; import type { MemberRequest } from '@/types/member'; diff --git a/frontend/src/pages/SearchPage.tsx b/frontend/src/pages/SearchPage.tsx index b14c4d6a..be5cf797 100644 --- a/frontend/src/pages/SearchPage.tsx +++ b/frontend/src/pages/SearchPage.tsx @@ -1,10 +1,10 @@ -import { Button, Heading, Spacing, Text, useTheme } from '@fun-eat/design-system'; +import { Button, Heading, Spacing, Text, Input, useTheme } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; import { Suspense, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { ErrorBoundary, ErrorComponent, Input, Loading, SvgIcon } from '@/components/Common'; +import { ErrorBoundary, ErrorComponent, Loading, SvgIcon } from '@/components/Common'; import { RecommendList, ProductSearchResultList, RecipeSearchResultList } from '@/components/Search'; import { SEARCH_PAGE_VARIANTS } from '@/constants'; import { useDebounce, useRoutePage } from '@/hooks/common'; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0a59dfb0..b0955c2e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1375,10 +1375,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== -"@fun-eat/design-system@^0.3.18": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.3.18.tgz#0c930437cd47923a9daffbaec748ef5db3b4d0c1" - integrity sha512-d1yfTLJLKPakFzf/wiDcLkRi5cit16hDJClH4+Mj6nMtChxMeUu3VU+i4oCJNqaNjZHDw9wOa+7L4kmIcKQnRg== +"@fun-eat/design-system@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.4.1.tgz#a486b58e9cc4db2535e5ec5370b711cfc5ca78b3" + integrity sha512-nmAu+H0qTmR161WrOVUD5pE66W7MtZl26+fASMBLZw7al9khTUwXkiI6vMr3sGaglih0TsB0bRSiMxWKtV4DlA== "@humanwhocodes/config-array@^0.11.11": version "0.11.11" From c6e61aa6bb9e76d1c291ce1ed6a4197aff05eee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=E1=B4=8F=CA=9F=CA=99=C9=AA=20=E2=98=94=EF=B8=8F?= Date: Sun, 19 Nov 2023 22:20:45 +0900 Subject: [PATCH 03/13] =?UTF-8?q?fix:=20toast=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=97=90=EC=84=9C=20imp?= =?UTF-8?q?ort=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#847)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts | 2 +- frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts b/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts index ae015303..38da1ec5 100644 --- a/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts +++ b/frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts @@ -1,7 +1,7 @@ +import { useToastActionContext } from '@fun-eat/design-system'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { recipeApi } from '@/apis'; -import { useToastActionContext } from '@/hooks/context'; import type { RecipeFavoriteRequestBody, RecipeDetail } from '@/types/recipe'; const headers = { 'Content-Type': 'application/json' }; diff --git a/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts b/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts index 6cdd51bc..c80a8c26 100644 --- a/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts +++ b/frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts @@ -1,7 +1,7 @@ +import { useToastActionContext } from '@fun-eat/design-system'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { productApi } from '@/apis'; -import { useToastActionContext } from '@/hooks/context'; import type { ReviewFavoriteRequestBody } from '@/types/review'; const headers = { 'Content-Type': 'application/json' }; From 546f945f3d7d34ab7413aeb6e7ab2bb1e13c5b91 Mon Sep 17 00:00:00 2001 From: wugawuga Date: Mon, 27 Nov 2023 16:35:17 +0900 Subject: [PATCH 04/13] =?UTF-8?q?chore:=20repo=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.gitignore => .gitignore | 0 README.md | 107 - backend/build.gradle => build.gradle | 0 frontend/.babelrc.json | 16 - frontend/.eslintrc.js | 113 - frontend/.gitignore | 9 - frontend/.nvmrc | 1 - frontend/.prettierrc | 7 - frontend/.storybook/main.ts | 65 - frontend/.storybook/preview-body.html | 129 - frontend/.storybook/preview.tsx | 61 - frontend/.stylelintrc.js | 15 - .../__tests__/hooks/useImageUploader.test.ts | 61 - .../__tests__/hooks/useStarRating.test.ts | 32 - frontend/__tests__/hooks/useTabMenu.test.ts | 30 - frontend/jest.config.js | 20 - frontend/package.json | 84 - frontend/public/assets/apple-icon-180x180.png | Bin 13932 -> 0 bytes frontend/public/assets/favicon-16x16.png | Bin 1448 -> 0 bytes frontend/public/assets/favicon-32x32.png | Bin 2349 -> 0 bytes frontend/public/assets/favicon.ico | Bin 1150 -> 0 bytes frontend/public/assets/og-image.png | Bin 20533 -> 0 bytes frontend/public/index.html | 33 - frontend/public/manifest.json | 23 - frontend/public/mockServiceWorker.js | 303 - frontend/public/robots.txt | 9 - frontend/public/sitemap.xml | 23 - frontend/src/apis/ApiClient.ts | 84 - frontend/src/apis/fetch.ts | 16 - frontend/src/apis/index.ts | 13 - frontend/src/assets/characters.svg | 63 - frontend/src/assets/logo.svg | 101 - frontend/src/assets/plate.svg | 64 - frontend/src/assets/samgakgimbab.svg | 87 - .../src/components/Common/Banner/Banner.tsx | 26 - .../CategoryFoodList.stories.tsx | 22 - .../CategoryFoodList/CategoryFoodList.tsx | 27 - .../CategoryFoodTab.stories.tsx | 22 - .../CategoryFoodTab/CategoryFoodTab.tsx | 78 - .../CategoryItem/CategoryItem.stories.tsx | 28 - .../Common/CategoryItem/CategoryItem.tsx | 65 - .../CategoryStoreList.stories.tsx | 22 - .../CategoryStoreList/CategoryStoreList.tsx | 27 - .../CategoryStoreTab.stories.tsx | 22 - .../CategoryStoreTab/CategoryStoreTab.tsx | 77 - .../Common/ErrorBoundary/ErrorBoundary.tsx | 53 - .../ErrorComponent/ErrorComponent.stories.tsx | 13 - .../Common/ErrorComponent/ErrorComponent.tsx | 5 - .../Common/Header/Header.stories.tsx | 13 - .../src/components/Common/Header/Header.tsx | 54 - .../ImageUploader/ImageUploader.stories.tsx | 13 - .../Common/ImageUploader/ImageUploader.tsx | 76 - .../Common/Loading/Loading.stories.tsx | 13 - .../src/components/Common/Loading/Loading.tsx | 49 - .../Common/MarkedText/MarkedText.tsx | 28 - .../NavigableSectionTitle.stories.tsx | 16 - .../NavigableSectionTitle.tsx | 35 - .../NavigationBar/NavigationBar.stories.tsx | 19 - .../Common/NavigationBar/NavigationBar.tsx | 63 - .../Common/RegisterButton/RegisterButton.tsx | 36 - .../ScrollButton/ScrollButton.stories.tsx | 13 - .../Common/ScrollButton/ScrollButton.tsx | 58 - .../SectionTitle/SectionTitle.stories.tsx | 23 - .../Common/SectionTitle/SectionTitle.tsx | 54 - .../Common/SortButton/SortButton.stories.tsx | 18 - .../Common/SortButton/SortButton.tsx | 32 - .../SortOptionList/SortOptionList.stories.tsx | 38 - .../Common/SortOptionList/SortOptionList.tsx | 71 - .../components/Common/Svg/SvgIcon.stories.tsx | 37 - .../src/components/Common/Svg/SvgIcon.tsx | 58 - .../src/components/Common/Svg/SvgSprite.tsx | 96 - .../Common/TabMenu/TabMenu.stories.tsx | 22 - .../src/components/Common/TabMenu/TabMenu.tsx | 67 - .../Common/TagList/TagList.stories.tsx | 18 - .../src/components/Common/TagList/TagList.tsx | 38 - frontend/src/components/Common/index.ts | 23 - frontend/src/components/Layout/AuthLayout.tsx | 20 - .../src/components/Layout/DefaultLayout.tsx | 30 - .../components/Layout/HeaderOnlyLayout.tsx | 29 - .../src/components/Layout/MinimalLayout.tsx | 26 - .../components/Layout/SimpleHeaderLayout.tsx | 31 - frontend/src/components/Layout/index.ts | 5 - .../MemberModifyInput/MemberModifyInput.tsx | 46 - .../MemberRecipeList.stories.tsx | 13 - .../MemberRecipeList/MemberRecipeList.tsx | 91 - .../MemberReviewItem.stories.tsx | 26 - .../MemberReviewItem/MemberReviewItem.tsx | 122 - .../MemberReviewList.stories.tsx | 13 - .../MemberReviewList/MemberReviewList.tsx | 91 - .../Members/MembersInfo/MembersInfo.tsx | 64 - .../MembersInfo/MyPageInfo.stories.tsx | 18 - frontend/src/components/Members/index.ts | 5 - .../ProductDetailItem.stories.tsx | 24 - .../ProductDetailItem/ProductDetailItem.tsx | 100 - .../ProductItem/ProductItem.stories.tsx | 18 - .../Product/ProductItem/ProductItem.tsx | 112 - .../ProductList/ProductList.stories.tsx | 13 - .../Product/ProductList/ProductList.tsx | 57 - .../ProductOverviewItem.stories.tsx | 23 - .../ProductOverviewItem.tsx | 60 - .../ProductRecipeList/ProductRecipeList.tsx | 77 - .../ProductTitle/ProductTitle.stories.tsx | 16 - .../Product/ProductTitle/ProductTitle.tsx | 52 - frontend/src/components/Product/index.ts | 6 - .../ProductRankingList.stories.tsx | 13 - .../ProductRankingList/ProductRankingList.tsx | 41 - .../RecipeRankingItem.stories.tsx | 19 - .../RecipeRankingItem/RecipeRankingItem.tsx | 122 - .../RecipeRankingList.stories.tsx | 13 - .../RecipeRankingList/RecipeRankingList.tsx | 32 - .../ReviewRankingItem.stories.tsx | 26 - .../ReviewRankingItem/ReviewRankingItem.tsx | 90 - .../ReviewRankingList.stories.tsx | 13 - .../ReviewRankingList/ReviewRankingList.tsx | 49 - frontend/src/components/Rank/index.ts | 5 - .../CommentForm/CommentForm.stories.tsx | 13 - .../Recipe/CommentForm/CommentForm.tsx | 105 - .../CommentItem/CommentItem.stories.tsx | 18 - .../Recipe/CommentItem/CommentItem.tsx | 50 - .../CommentList/CommentList.stories.tsx | 13 - .../Recipe/CommentList/CommentList.tsx | 37 - .../RecipeDetailTextarea.stories.tsx | 25 - .../RecipeDetailTextarea.tsx | 49 - .../RecipeFavoriteButton.tsx | 43 - .../Recipe/RecipeItem/RecipeItem.stories.tsx | 18 - .../Recipe/RecipeItem/RecipeItem.tsx | 114 - .../Recipe/RecipeList/RecipeList.stories.tsx | 13 - .../Recipe/RecipeList/RecipeList.tsx | 49 - .../RecipeNameInput.stories.tsx | 22 - .../RecipeNameInput/RecipeNameInput.tsx | 58 - .../RecipeRegisterForm.stories.tsx | 22 - .../RecipeRegisterForm/RecipeRegisterForm.tsx | 129 - .../RecipeUsedProducts.stories.tsx | 25 - .../RecipeUsedProducts/RecipeUsedProducts.tsx | 125 - .../SearchedProductList.tsx | 96 - frontend/src/components/Recipe/index.ts | 10 - .../BestReviewItem/BestReviewItem.stories.tsx | 17 - .../Review/BestReviewItem/BestReviewItem.tsx | 103 - .../Review/RebuyCheckbox/RebuyCheckbox.tsx | 24 - .../ReviewFavoriteButton.tsx | 69 - .../Review/ReviewItem/ReviewItem.stories.tsx | 37 - .../Review/ReviewItem/ReviewItem.tsx | 105 - .../Review/ReviewList/ReviewList.tsx | 52 - .../ReviewRegisterForm.stories.tsx | 26 - .../ReviewRegisterForm/ReviewRegisterForm.tsx | 172 - .../ReviewTagItem/ReviewTagItem.stories.tsx | 33 - .../Review/ReviewTagItem/ReviewTagItem.tsx | 65 - .../ReviewTagList/ReviewTagList.stories.tsx | 25 - .../Review/ReviewTagList/ReviewTagList.tsx | 114 - .../ReviewTextarea/ReviewTextarea.stories.tsx | 25 - .../Review/ReviewTextarea/ReviewTextarea.tsx | 59 - .../Review/StarRate/StarRate.stories.tsx | 22 - .../components/Review/StarRate/StarRate.tsx | 67 - frontend/src/components/Review/index.ts | 7 - .../ProductSearchResultList.tsx | 55 - .../RecipeSearchResultList.tsx | 48 - .../Search/RecommendList/RecommendList.tsx | 92 - frontend/src/components/Search/index.ts | 3 - frontend/src/constants/index.ts | 75 - frontend/src/constants/path.ts | 9 - frontend/src/contexts/CategoryContext.tsx | 57 - frontend/src/contexts/RecipeFormContext.tsx | 69 - frontend/src/contexts/ReviewFormContext.tsx | 67 - frontend/src/hooks/common/index.ts | 13 - frontend/src/hooks/common/useDebounce.ts | 14 - frontend/src/hooks/common/useEnterKeyDown.ts | 18 - frontend/src/hooks/common/useFormData.ts | 29 - frontend/src/hooks/common/useGA.ts | 20 - frontend/src/hooks/common/useImageUploader.ts | 64 - .../hooks/common/useIntersectionObserver.ts | 51 - .../src/hooks/common/useRouteChangeTracker.ts | 26 - frontend/src/hooks/common/useRoutePage.ts | 19 - frontend/src/hooks/common/useScroll.ts | 22 - .../src/hooks/common/useScrollRestoration.ts | 36 - frontend/src/hooks/common/useSortOption.ts | 15 - frontend/src/hooks/common/useTabMenu.ts | 26 - frontend/src/hooks/common/useTimeout.ts | 34 - frontend/src/hooks/context/index.ts | 6 - .../hooks/context/useCategoryActionContext.ts | 14 - .../hooks/context/useCategoryValueContext.ts | 14 - .../context/useRecipeFormActionContext.ts | 14 - .../context/useRecipeFormValueContext.ts | 15 - .../context/useReviewFormActionContext.ts | 15 - .../context/useReviewFormValueContext.ts | 15 - frontend/src/hooks/queries/banner/index.ts | 1 - .../hooks/queries/banner/useBannerQuery.ts | 16 - frontend/src/hooks/queries/index.ts | 2 - frontend/src/hooks/queries/members/index.ts | 6 - .../hooks/queries/members/useDeleteReview.ts | 20 - .../members/useInfiniteMemberRecipeQuery.ts | 22 - .../members/useInfiniteMemberReviewQuery.ts | 22 - .../queries/members/useLogoutMutation.ts | 30 - .../members/useMemberModifyMutation.ts | 18 - .../hooks/queries/members/useMemberQuery.ts | 19 - frontend/src/hooks/queries/product/index.ts | 6 - .../hooks/queries/product/useCategoryQuery.ts | 22 - .../product/useInfiniteProductRecipesQuery.ts | 30 - .../product/useInfiniteProductReviewsQuery.ts | 30 - .../product/useInfiniteProductsQuery.ts | 31 - .../queries/product/useProductDetailQuery.ts | 16 - frontend/src/hooks/queries/rank/index.ts | 4 - .../hooks/queries/rank/useBestReviewQuery.ts | 21 - .../queries/rank/useProductRankingQuery.ts | 16 - .../queries/rank/useRecipeRankingQuery.ts | 16 - .../queries/rank/useReviewRankingQuery.ts | 16 - frontend/src/hooks/queries/recipe/index.ts | 6 - .../recipe/useInfiniteRecipeCommentQuery.ts | 39 - .../queries/recipe/useInfiniteRecipesQuery.ts | 22 - .../recipe/useRecipeCommentMutation.ts | 24 - .../queries/recipe/useRecipeDetailQuery.ts | 16 - .../recipe/useRecipeFavoriteMutation.ts | 54 - .../recipe/useRecipeRegisterFormMutation.ts | 19 - frontend/src/hooks/queries/review/index.ts | 4 - .../queries/review/useReviewDetailQuery.ts | 16 - .../review/useReviewFavoriteMutation.ts | 33 - .../review/useReviewRegisterFormMutation.ts | 14 - .../queries/review/useReviewTagsQuery.ts | 16 - frontend/src/hooks/queries/search/index.ts | 3 - ...eInfiniteProductSearchAutocompleteQuery.ts | 27 - .../useInfiniteProductSearchResultsQuery.ts | 27 - .../useInfiniteRecipeSearchResultsQuery.ts | 27 - .../queries/useSuspendedInfiniteQuery.ts | 101 - .../src/hooks/queries/useSuspendedQuery.ts | 119 - frontend/src/hooks/review/index.ts | 2 - frontend/src/hooks/review/useDisplayTag.ts | 21 - .../src/hooks/review/useStarRatingHover.ts | 17 - frontend/src/hooks/search/index.ts | 1 - frontend/src/hooks/search/useSearch.ts | 85 - frontend/src/index.tsx | 50 - frontend/src/mocks/browser.ts | 25 - frontend/src/mocks/data/banners.json | 17 - frontend/src/mocks/data/comments.json | 33 - frontend/src/mocks/data/foodCategory.json | 7 - frontend/src/mocks/data/memberRecipes.json | 63 - frontend/src/mocks/data/memberReviews.json | 30 - frontend/src/mocks/data/members.json | 4 - frontend/src/mocks/data/pbProducts.json | 85 - frontend/src/mocks/data/productDetail.json | 26 - frontend/src/mocks/data/productDetails.json | 44 - .../src/mocks/data/productRankingList.json | 22 - .../src/mocks/data/productSearchResults.json | 48 - frontend/src/mocks/data/products.json | 21 - frontend/src/mocks/data/recipeDetail.json | 26 - .../src/mocks/data/recipeRankingList.json | 37 - frontend/src/mocks/data/recipes.json | 80 - frontend/src/mocks/data/reviewDetail.json | 27 - .../src/mocks/data/reviewRankingList.json | 24 - frontend/src/mocks/data/reviewTagList.json | 69 - frontend/src/mocks/data/reviews.json | 62 - .../src/mocks/data/searchingProducts.json | 32 - frontend/src/mocks/data/storeCategory.json | 6 - frontend/src/mocks/handlers/bannerHandlers.ts | 9 - frontend/src/mocks/handlers/index.ts | 9 - frontend/src/mocks/handlers/loginHandlers.ts | 21 - frontend/src/mocks/handlers/logoutHandlers.ts | 11 - frontend/src/mocks/handlers/memberHandlers.ts | 62 - .../src/mocks/handlers/productHandlers.ts | 75 - .../src/mocks/handlers/rankingHandlers.ts | 24 - frontend/src/mocks/handlers/recipeHandlers.ts | 106 - frontend/src/mocks/handlers/reviewHandlers.ts | 81 - frontend/src/mocks/handlers/searchHandlers.ts | 68 - frontend/src/mocks/handlers/utils.ts | 12 - frontend/src/pages/AuthPage.tsx | 65 - frontend/src/pages/HomePage.tsx | 131 - frontend/src/pages/IntegratedSearchPage.tsx | 117 - frontend/src/pages/LoginPage.tsx | 82 - frontend/src/pages/MemberModifyPage.tsx | 155 - frontend/src/pages/MemberPage.tsx | 40 - frontend/src/pages/MemberRecipePage.tsx | 32 - frontend/src/pages/MemberReviewPage.tsx | 32 - frontend/src/pages/NotFoundPage.tsx | 42 - frontend/src/pages/ProductDetailPage.tsx | 202 - frontend/src/pages/ProductListPage.tsx | 95 - frontend/src/pages/RecipeDetailPage.tsx | 140 - frontend/src/pages/RecipePage.tsx | 132 - frontend/src/pages/ReviewDetailPage.tsx | 132 - frontend/src/pages/SearchPage.tsx | 142 - frontend/src/router/App.tsx | 64 - frontend/src/router/index.tsx | 193 - frontend/src/service/channelTalk.ts | 202 - frontend/src/styles/animations.ts | 23 - frontend/src/styles/font.ts | 14 - frontend/src/styles/globalStyle.ts | 18 - frontend/src/types/banner.ts | 5 - frontend/src/types/common.ts | 55 - frontend/src/types/images.d.ts | 20 - frontend/src/types/member.ts | 8 - frontend/src/types/product.ts | 30 - frontend/src/types/ranking.ts | 25 - frontend/src/types/recipe.ts | 48 - frontend/src/types/response.ts | 71 - frontend/src/types/review.ts | 49 - frontend/src/types/search.ts | 8 - frontend/src/types/styled.d.ts | 7 - frontend/src/utils/category.ts | 4 - frontend/src/utils/convertTagColor.ts | 16 - frontend/src/utils/date.ts | 10 - frontend/src/utils/displaySlice.ts | 5 - frontend/src/utils/localStorage.ts | 26 - frontend/src/utils/uuid.ts | 1 - frontend/tsconfig.json | 23 - frontend/webpack.common.js | 41 - frontend/webpack.dev.js | 20 - frontend/webpack.prod.js | 32 - frontend/yarn.lock | 11562 ---------------- .../wrapper/gradle-wrapper.jar | Bin .../wrapper/gradle-wrapper.properties | 0 backend/gradlew => gradlew | 0 backend/gradlew.bat => gradlew.bat | 0 backend/settings.gradle => settings.gradle | 0 .../java/com/funeat/FuneatApplication.java | 0 .../admin/application/AdminChecker.java | 0 .../admin/application/AdminService.java | 0 .../funeat/admin/domain/AdminAuthInfo.java | 0 .../admin/dto/AdminCategoryResponse.java | 0 .../admin/dto/AdminProductResponse.java | 0 .../admin/dto/AdminProductSearchResponse.java | 0 .../funeat/admin/dto/AdminReviewResponse.java | 0 .../admin/dto/AdminReviewSearchResponse.java | 0 .../admin/dto/ProductCreateRequest.java | 0 .../admin/dto/ProductSearchCondition.java | 0 .../admin/dto/ProductUpdateRequest.java | 0 .../admin/dto/ReviewSearchCondition.java | 0 .../admin/presentation/AdminController.java | 0 .../presentation/AdminLoginController.java | 0 .../repository/AdminProductRepository.java | 0 .../repository/AdminReviewRepository.java | 0 .../AdminProductSpecification.java | 0 .../AdminReviewSpecification.java | 0 .../admin/util/AdminCheckInterceptor.java | 0 .../funeat/auth/application/AuthService.java | 0 .../com/funeat/auth/dto/KakaoTokenDto.java | 0 .../com/funeat/auth/dto/KakaoUserInfoDto.java | 0 .../java/com/funeat/auth/dto/LoginInfo.java | 0 .../java/com/funeat/auth/dto/SignUserDto.java | 0 .../com/funeat/auth/dto/TokenResponse.java | 0 .../java/com/funeat/auth/dto/UserInfoDto.java | 0 .../funeat/auth/exception/AuthErrorCode.java | 0 .../funeat/auth/exception/AuthException.java | 0 .../auth/presentation/AuthApiController.java | 0 .../auth/presentation/AuthController.java | 0 .../auth/util/AuthArgumentResolver.java | 0 .../auth/util/AuthHandlerInterceptor.java | 0 .../auth/util/AuthenticationPrincipal.java | 0 .../auth/util/KakaoPlatformUserProvider.java | 0 .../auth/util/PlatformUserProvider.java | 0 .../banner/application/BannerService.java | 0 .../java/com/funeat/banner/domain/Banner.java | 0 .../com/funeat/banner/dto/BannerResponse.java | 0 .../banner/persistence/BannerRepository.java | 0 .../presentation/BannerApiController.java | 0 .../banner/presentation/BannerController.java | 0 .../com/funeat/comment/domain/Comment.java | 0 .../persistence/CommentRepository.java | 0 .../specification/CommentSpecification.java | 0 ...PageableHandlerMethodArgumentResolver.java | 0 .../java/com/funeat/common/ImageUploader.java | 0 .../java/com/funeat/common/OpenApiConfig.java | 0 .../common/StringToCategoryTypeConverter.java | 0 .../java/com/funeat/common/WebConfig.java | 0 .../java/com/funeat/common/dto/PageDto.java | 0 .../common/exception/CommonException.java | 0 .../com/funeat/common/logging/Logging.java | 0 .../funeat/common/logging/LoggingAspect.java | 0 .../common/repository/BaseRepository.java | 0 .../common/repository/BaseRepositoryImpl.java | 0 .../java/com/funeat/common/s3/AwsConfig.java | 0 .../java/com/funeat/common/s3/S3Uploader.java | 0 .../com/funeat/exception/CommonErrorCode.java | 0 .../java/com/funeat/exception/ErrorCode.java | 0 .../com/funeat/exception/GlobalException.java | 0 .../presentation/GlobalControllerAdvice.java | 0 .../member/application/MemberService.java | 0 .../java/com/funeat/member/domain/Member.java | 0 .../domain/favorite/RecipeFavorite.java | 0 .../domain/favorite/ReviewFavorite.java | 0 .../member/dto/MemberProfileResponse.java | 0 .../funeat/member/dto/MemberRecipeDto.java | 0 .../member/dto/MemberRecipeProductDto.java | 0 .../member/dto/MemberRecipesResponse.java | 0 .../com/funeat/member/dto/MemberRequest.java | 0 .../funeat/member/dto/MemberReviewDto.java | 0 .../member/dto/MemberReviewsResponse.java | 0 .../member/exception/MemberErrorCode.java | 0 .../member/exception/MemberException.java | 0 .../member/persistence/MemberRepository.java | 0 .../persistence/RecipeFavoriteRepository.java | 0 .../persistence/ReviewFavoriteRepository.java | 0 .../presentation/MemberApiController.java | 0 .../member/presentation/MemberController.java | 0 .../product/application/CategoryService.java | 0 .../product/application/ProductService.java | 0 .../com/funeat/product/domain/Category.java | 0 .../funeat/product/domain/CategoryType.java | 0 .../com/funeat/product/domain/Product.java | 0 .../funeat/product/domain/ProductRecipe.java | 0 .../funeat/product/dto/CategoryResponse.java | 0 .../product/dto/ProductInCategoryDto.java | 0 .../funeat/product/dto/ProductResponse.java | 0 .../product/dto/ProductReviewCountDto.java | 0 .../product/dto/ProductSortCondition.java | 0 .../dto/ProductsInCategoryResponse.java | 0 .../funeat/product/dto/RankingProductDto.java | 0 .../product/dto/RankingProductsResponse.java | 0 .../funeat/product/dto/SearchProductDto.java | 0 .../product/dto/SearchProductResultDto.java | 0 .../dto/SearchProductResultsResponse.java | 0 .../product/dto/SearchProductsResponse.java | 0 .../product/exception/CategoryErrorCode.java | 0 .../product/exception/CategoryException.java | 0 .../product/exception/ProductErrorCode.java | 0 .../product/exception/ProductException.java | 0 .../persistence/CategoryRepository.java | 0 .../persistence/ProductRecipeRepository.java | 0 .../persistence/ProductRepository.java | 0 .../persistence/ProductSpecification.java | 0 .../presentation/CategoryApiController.java | 0 .../presentation/CategoryController.java | 0 .../presentation/ProductApiController.java | 0 .../presentation/ProductController.java | 0 .../recipe/application/RecipeService.java | 0 .../java/com/funeat/recipe/domain/Recipe.java | 0 .../com/funeat/recipe/domain/RecipeImage.java | 0 .../funeat/recipe/dto/ProductRecipeDto.java | 0 .../funeat/recipe/dto/RankingRecipeDto.java | 0 .../recipe/dto/RankingRecipesResponse.java | 0 .../funeat/recipe/dto/RecipeAuthorDto.java | 0 .../recipe/dto/RecipeCommentCondition.java | 0 .../dto/RecipeCommentCreateRequest.java | 0 .../dto/RecipeCommentMemberResponse.java | 0 .../recipe/dto/RecipeCommentResponse.java | 0 .../recipe/dto/RecipeCommentsResponse.java | 0 .../recipe/dto/RecipeCreateRequest.java | 0 .../recipe/dto/RecipeDetailResponse.java | 0 .../java/com/funeat/recipe/dto/RecipeDto.java | 0 .../recipe/dto/RecipeFavoriteRequest.java | 0 .../recipe/dto/SearchRecipeResultDto.java | 0 .../dto/SearchRecipeResultsResponse.java | 0 .../recipe/dto/SortingRecipesResponse.java | 0 .../recipe/exception/RecipeErrorCode.java | 0 .../recipe/exception/RecipeException.java | 0 .../persistence/RecipeImageRepository.java | 0 .../recipe/persistence/RecipeRepository.java | 0 .../presentation/RecipeApiController.java | 0 .../recipe/presentation/RecipeController.java | 0 .../util/RecipeDetailHandlerInterceptor.java | 0 .../recipe/util/RecipeHandlerInterceptor.java | 0 .../review/application/ReviewDeleteEvent.java | 0 .../ReviewDeleteEventListener.java | 0 .../review/application/ReviewService.java | 0 .../java/com/funeat/review/domain/Review.java | 0 .../com/funeat/review/domain/ReviewTag.java | 0 .../dto/MostFavoriteReviewResponse.java | 0 .../funeat/review/dto/RankingReviewDto.java | 0 .../review/dto/RankingReviewsResponse.java | 0 .../review/dto/ReviewCreateRequest.java | 0 .../review/dto/ReviewDetailResponse.java | 0 .../review/dto/ReviewFavoriteRequest.java | 0 .../funeat/review/dto/SortingReviewDto.java | 0 .../dto/SortingReviewDtoWithoutTag.java | 0 .../review/dto/SortingReviewRequest.java | 0 .../review/dto/SortingReviewsResponse.java | 0 .../review/exception/ReviewErrorCode.java | 0 .../review/exception/ReviewException.java | 0 .../persistence/ReviewCustomRepository.java | 0 .../review/persistence/ReviewRepository.java | 0 .../persistence/ReviewRepositoryImpl.java | 0 .../persistence/ReviewTagRepository.java | 0 .../presentation/ReviewApiController.java | 0 .../review/presentation/ReviewController.java | 0 .../specification/LongTypeReviewSortSpec.java | 0 .../SortingReviewSpecification.java | 0 .../funeat/tag/application/TagService.java | 0 .../main/java/com/funeat/tag/domain/Tag.java | 0 .../java/com/funeat/tag/domain/TagType.java | 0 .../main/java/com/funeat/tag/dto/TagDto.java | 0 .../java/com/funeat/tag/dto/TagsResponse.java | 0 .../funeat/tag/persistence/TagRepository.java | 0 .../tag/presentation/TagApiController.java | 0 .../tag/presentation/TagController.java | 0 .../main/resources/application-dev.yml | 0 .../main/resources/application-local.yml | 0 .../main/resources/application-prod.yml | 0 .../main/resources/application.yml | 0 .../main/resources/logback-spring-dev.xml | 0 .../main/resources/logback-spring-prod.xml | 0 .../main/resources/logback-spring.xml | 0 .../main/resources/logback-variables.yml | 0 .../com/funeat/FuneatApplicationTests.java | 0 .../acceptance/auth/AuthAcceptanceTest.java | 0 .../funeat/acceptance/auth/LoginSteps.java | 0 .../banner/BannerAcceptanceTest.java | 0 .../funeat/acceptance/banner/BannerSteps.java | 0 .../acceptance/common/AcceptanceTest.java | 0 .../funeat/acceptance/common/CommonSteps.java | 0 .../common/TestPlatformUserProvider.java | 0 .../member/MemberAcceptanceTest.java | 0 .../funeat/acceptance/member/MemberSteps.java | 0 .../product/CategoryAcceptanceTest.java | 0 .../acceptance/product/CategorySteps.java | 0 .../product/ProductAcceptanceTest.java | 0 .../acceptance/product/ProductSteps.java | 0 .../recipe/RecipeAcceptanceTest.java | 0 .../funeat/acceptance/recipe/RecipeSteps.java | 0 .../review/ReviewAcceptanceTest.java | 0 .../funeat/acceptance/review/ReviewSteps.java | 0 .../acceptance/tag/TagAcceptanceTest.java | 0 .../com/funeat/acceptance/tag/TagSteps.java | 0 .../auth/application/AuthServiceTest.java | 0 .../banner/application/BannerServiceTest.java | 0 .../persistence/BannerRepositoryTest.java | 0 .../java/com/funeat/common/DataCleaner.java | 0 .../com/funeat/common/DataClearExtension.java | 0 .../java/com/funeat/common/EventTest.java | 0 .../com/funeat/common/RepositoryTest.java | 0 .../java/com/funeat/common/ServiceTest.java | 0 .../com/funeat/common/TestImageUploader.java | 0 .../com/funeat/fixture/BannerFixture.java | 0 .../com/funeat/fixture/CategoryFixture.java | 0 .../java/com/funeat/fixture/ImageFixture.java | 0 .../com/funeat/fixture/MemberFixture.java | 0 .../java/com/funeat/fixture/PageFixture.java | 0 .../com/funeat/fixture/ProductFixture.java | 0 .../com/funeat/fixture/RecipeFixture.java | 0 .../com/funeat/fixture/ReviewFixture.java | 0 .../java/com/funeat/fixture/ScoreFixture.java | 0 .../java/com/funeat/fixture/TagFixture.java | 0 .../member/application/MemberServiceTest.java | 0 .../member/application/TestMemberService.java | 0 .../com/funeat/member/domain/MemberTest.java | 0 .../domain/favorite/RecipeFavoriteTest.java | 0 .../persistence/MemberRepositoryTest.java | 0 .../RecipeFavoriteRepositoryTest.java | 0 .../ReviewFavoriteRepositoryTest.java | 0 .../application/ProductServiceTest.java | 0 .../funeat/product/domain/ProductTest.java | 0 .../domain/favorite/ReviewFavoriteTest.java | 0 .../persistence/CategoryRepositoryTest.java | 0 .../ProductRecipeRepositoryTest.java | 0 .../persistence/ProductRepositoryTest.java | 0 .../recipe/application/RecipeServiceTest.java | 0 .../com/funeat/recipe/domain/RecipeTest.java | 0 .../RecipeImageRepositoryTest.java | 0 .../persistence/RecipeRepositoryTest.java | 0 .../ReviewDeleteEventListenerTest.java | 0 .../review/application/ReviewServiceTest.java | 0 .../com/funeat/review/domain/ReviewTest.java | 0 .../persistence/ReviewRepositoryTest.java | 0 .../persistence/ReviewTagRepositoryTest.java | 0 .../tag/persistence/TagRepositoryTest.java | 0 .../test/resources/application.yml | 0 551 files changed, 24251 deletions(-) rename backend/.gitignore => .gitignore (100%) delete mode 100644 README.md rename backend/build.gradle => build.gradle (100%) delete mode 100644 frontend/.babelrc.json delete mode 100644 frontend/.eslintrc.js delete mode 100644 frontend/.gitignore delete mode 100644 frontend/.nvmrc delete mode 100644 frontend/.prettierrc delete mode 100644 frontend/.storybook/main.ts delete mode 100644 frontend/.storybook/preview-body.html delete mode 100644 frontend/.storybook/preview.tsx delete mode 100644 frontend/.stylelintrc.js delete mode 100644 frontend/__tests__/hooks/useImageUploader.test.ts delete mode 100644 frontend/__tests__/hooks/useStarRating.test.ts delete mode 100644 frontend/__tests__/hooks/useTabMenu.test.ts delete mode 100644 frontend/jest.config.js delete mode 100644 frontend/package.json delete mode 100644 frontend/public/assets/apple-icon-180x180.png delete mode 100644 frontend/public/assets/favicon-16x16.png delete mode 100644 frontend/public/assets/favicon-32x32.png delete mode 100644 frontend/public/assets/favicon.ico delete mode 100644 frontend/public/assets/og-image.png delete mode 100644 frontend/public/index.html delete mode 100644 frontend/public/manifest.json delete mode 100644 frontend/public/mockServiceWorker.js delete mode 100644 frontend/public/robots.txt delete mode 100644 frontend/public/sitemap.xml delete mode 100644 frontend/src/apis/ApiClient.ts delete mode 100644 frontend/src/apis/fetch.ts delete mode 100644 frontend/src/apis/index.ts delete mode 100644 frontend/src/assets/characters.svg delete mode 100644 frontend/src/assets/logo.svg delete mode 100644 frontend/src/assets/plate.svg delete mode 100644 frontend/src/assets/samgakgimbab.svg delete mode 100644 frontend/src/components/Common/Banner/Banner.tsx delete mode 100644 frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx delete mode 100644 frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx delete mode 100644 frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx delete mode 100644 frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx delete mode 100644 frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx delete mode 100644 frontend/src/components/Common/CategoryItem/CategoryItem.tsx delete mode 100644 frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx delete mode 100644 frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx delete mode 100644 frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx delete mode 100644 frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx delete mode 100644 frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx delete mode 100644 frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx delete mode 100644 frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx delete mode 100644 frontend/src/components/Common/Header/Header.stories.tsx delete mode 100644 frontend/src/components/Common/Header/Header.tsx delete mode 100644 frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx delete mode 100644 frontend/src/components/Common/ImageUploader/ImageUploader.tsx delete mode 100644 frontend/src/components/Common/Loading/Loading.stories.tsx delete mode 100644 frontend/src/components/Common/Loading/Loading.tsx delete mode 100644 frontend/src/components/Common/MarkedText/MarkedText.tsx delete mode 100644 frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx delete mode 100644 frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx delete mode 100644 frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx delete mode 100644 frontend/src/components/Common/NavigationBar/NavigationBar.tsx delete mode 100644 frontend/src/components/Common/RegisterButton/RegisterButton.tsx delete mode 100644 frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx delete mode 100644 frontend/src/components/Common/ScrollButton/ScrollButton.tsx delete mode 100644 frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx delete mode 100644 frontend/src/components/Common/SectionTitle/SectionTitle.tsx delete mode 100644 frontend/src/components/Common/SortButton/SortButton.stories.tsx delete mode 100644 frontend/src/components/Common/SortButton/SortButton.tsx delete mode 100644 frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx delete mode 100644 frontend/src/components/Common/SortOptionList/SortOptionList.tsx delete mode 100644 frontend/src/components/Common/Svg/SvgIcon.stories.tsx delete mode 100644 frontend/src/components/Common/Svg/SvgIcon.tsx delete mode 100644 frontend/src/components/Common/Svg/SvgSprite.tsx delete mode 100644 frontend/src/components/Common/TabMenu/TabMenu.stories.tsx delete mode 100644 frontend/src/components/Common/TabMenu/TabMenu.tsx delete mode 100644 frontend/src/components/Common/TagList/TagList.stories.tsx delete mode 100644 frontend/src/components/Common/TagList/TagList.tsx delete mode 100644 frontend/src/components/Common/index.ts delete mode 100644 frontend/src/components/Layout/AuthLayout.tsx delete mode 100644 frontend/src/components/Layout/DefaultLayout.tsx delete mode 100644 frontend/src/components/Layout/HeaderOnlyLayout.tsx delete mode 100644 frontend/src/components/Layout/MinimalLayout.tsx delete mode 100644 frontend/src/components/Layout/SimpleHeaderLayout.tsx delete mode 100644 frontend/src/components/Layout/index.ts delete mode 100644 frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx delete mode 100644 frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx delete mode 100644 frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx delete mode 100644 frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx delete mode 100644 frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx delete mode 100644 frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx delete mode 100644 frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx delete mode 100644 frontend/src/components/Members/MembersInfo/MembersInfo.tsx delete mode 100644 frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx delete mode 100644 frontend/src/components/Members/index.ts delete mode 100644 frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx delete mode 100644 frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx delete mode 100644 frontend/src/components/Product/ProductItem/ProductItem.stories.tsx delete mode 100644 frontend/src/components/Product/ProductItem/ProductItem.tsx delete mode 100644 frontend/src/components/Product/ProductList/ProductList.stories.tsx delete mode 100644 frontend/src/components/Product/ProductList/ProductList.tsx delete mode 100644 frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx delete mode 100644 frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx delete mode 100644 frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx delete mode 100644 frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx delete mode 100644 frontend/src/components/Product/ProductTitle/ProductTitle.tsx delete mode 100644 frontend/src/components/Product/index.ts delete mode 100644 frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx delete mode 100644 frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx delete mode 100644 frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx delete mode 100644 frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx delete mode 100644 frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx delete mode 100644 frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx delete mode 100644 frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx delete mode 100644 frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx delete mode 100644 frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx delete mode 100644 frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx delete mode 100644 frontend/src/components/Rank/index.ts delete mode 100644 frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx delete mode 100644 frontend/src/components/Recipe/CommentForm/CommentForm.tsx delete mode 100644 frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx delete mode 100644 frontend/src/components/Recipe/CommentItem/CommentItem.tsx delete mode 100644 frontend/src/components/Recipe/CommentList/CommentList.stories.tsx delete mode 100644 frontend/src/components/Recipe/CommentList/CommentList.tsx delete mode 100644 frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx delete mode 100644 frontend/src/components/Recipe/RecipeFavoriteButton/RecipeFavoriteButton.tsx delete mode 100644 frontend/src/components/Recipe/RecipeItem/RecipeItem.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeItem/RecipeItem.tsx delete mode 100644 frontend/src/components/Recipe/RecipeList/RecipeList.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeList/RecipeList.tsx delete mode 100644 frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeNameInput/RecipeNameInput.tsx delete mode 100644 frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeRegisterForm/RecipeRegisterForm.tsx delete mode 100644 frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.stories.tsx delete mode 100644 frontend/src/components/Recipe/RecipeUsedProducts/RecipeUsedProducts.tsx delete mode 100644 frontend/src/components/Recipe/RecipeUsedProducts/SearchedProductList.tsx delete mode 100644 frontend/src/components/Recipe/index.ts delete mode 100644 frontend/src/components/Review/BestReviewItem/BestReviewItem.stories.tsx delete mode 100644 frontend/src/components/Review/BestReviewItem/BestReviewItem.tsx delete mode 100644 frontend/src/components/Review/RebuyCheckbox/RebuyCheckbox.tsx delete mode 100644 frontend/src/components/Review/ReviewFavoriteButton/ReviewFavoriteButton.tsx delete mode 100644 frontend/src/components/Review/ReviewItem/ReviewItem.stories.tsx delete mode 100644 frontend/src/components/Review/ReviewItem/ReviewItem.tsx delete mode 100644 frontend/src/components/Review/ReviewList/ReviewList.tsx delete mode 100644 frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.stories.tsx delete mode 100644 frontend/src/components/Review/ReviewRegisterForm/ReviewRegisterForm.tsx delete mode 100644 frontend/src/components/Review/ReviewTagItem/ReviewTagItem.stories.tsx delete mode 100644 frontend/src/components/Review/ReviewTagItem/ReviewTagItem.tsx delete mode 100644 frontend/src/components/Review/ReviewTagList/ReviewTagList.stories.tsx delete mode 100644 frontend/src/components/Review/ReviewTagList/ReviewTagList.tsx delete mode 100644 frontend/src/components/Review/ReviewTextarea/ReviewTextarea.stories.tsx delete mode 100644 frontend/src/components/Review/ReviewTextarea/ReviewTextarea.tsx delete mode 100644 frontend/src/components/Review/StarRate/StarRate.stories.tsx delete mode 100644 frontend/src/components/Review/StarRate/StarRate.tsx delete mode 100644 frontend/src/components/Review/index.ts delete mode 100644 frontend/src/components/Search/ProductSearchResultList/ProductSearchResultList.tsx delete mode 100644 frontend/src/components/Search/RecipeSearchResultList/RecipeSearchResultList.tsx delete mode 100644 frontend/src/components/Search/RecommendList/RecommendList.tsx delete mode 100644 frontend/src/components/Search/index.ts delete mode 100644 frontend/src/constants/index.ts delete mode 100644 frontend/src/constants/path.ts delete mode 100644 frontend/src/contexts/CategoryContext.tsx delete mode 100644 frontend/src/contexts/RecipeFormContext.tsx delete mode 100644 frontend/src/contexts/ReviewFormContext.tsx delete mode 100644 frontend/src/hooks/common/index.ts delete mode 100644 frontend/src/hooks/common/useDebounce.ts delete mode 100644 frontend/src/hooks/common/useEnterKeyDown.ts delete mode 100644 frontend/src/hooks/common/useFormData.ts delete mode 100644 frontend/src/hooks/common/useGA.ts delete mode 100644 frontend/src/hooks/common/useImageUploader.ts delete mode 100644 frontend/src/hooks/common/useIntersectionObserver.ts delete mode 100644 frontend/src/hooks/common/useRouteChangeTracker.ts delete mode 100644 frontend/src/hooks/common/useRoutePage.ts delete mode 100644 frontend/src/hooks/common/useScroll.ts delete mode 100644 frontend/src/hooks/common/useScrollRestoration.ts delete mode 100644 frontend/src/hooks/common/useSortOption.ts delete mode 100644 frontend/src/hooks/common/useTabMenu.ts delete mode 100644 frontend/src/hooks/common/useTimeout.ts delete mode 100644 frontend/src/hooks/context/index.ts delete mode 100644 frontend/src/hooks/context/useCategoryActionContext.ts delete mode 100644 frontend/src/hooks/context/useCategoryValueContext.ts delete mode 100644 frontend/src/hooks/context/useRecipeFormActionContext.ts delete mode 100644 frontend/src/hooks/context/useRecipeFormValueContext.ts delete mode 100644 frontend/src/hooks/context/useReviewFormActionContext.ts delete mode 100644 frontend/src/hooks/context/useReviewFormValueContext.ts delete mode 100644 frontend/src/hooks/queries/banner/index.ts delete mode 100644 frontend/src/hooks/queries/banner/useBannerQuery.ts delete mode 100644 frontend/src/hooks/queries/index.ts delete mode 100644 frontend/src/hooks/queries/members/index.ts delete mode 100644 frontend/src/hooks/queries/members/useDeleteReview.ts delete mode 100644 frontend/src/hooks/queries/members/useInfiniteMemberRecipeQuery.ts delete mode 100644 frontend/src/hooks/queries/members/useInfiniteMemberReviewQuery.ts delete mode 100644 frontend/src/hooks/queries/members/useLogoutMutation.ts delete mode 100644 frontend/src/hooks/queries/members/useMemberModifyMutation.ts delete mode 100644 frontend/src/hooks/queries/members/useMemberQuery.ts delete mode 100644 frontend/src/hooks/queries/product/index.ts delete mode 100644 frontend/src/hooks/queries/product/useCategoryQuery.ts delete mode 100644 frontend/src/hooks/queries/product/useInfiniteProductRecipesQuery.ts delete mode 100644 frontend/src/hooks/queries/product/useInfiniteProductReviewsQuery.ts delete mode 100644 frontend/src/hooks/queries/product/useInfiniteProductsQuery.ts delete mode 100644 frontend/src/hooks/queries/product/useProductDetailQuery.ts delete mode 100644 frontend/src/hooks/queries/rank/index.ts delete mode 100644 frontend/src/hooks/queries/rank/useBestReviewQuery.ts delete mode 100644 frontend/src/hooks/queries/rank/useProductRankingQuery.ts delete mode 100644 frontend/src/hooks/queries/rank/useRecipeRankingQuery.ts delete mode 100644 frontend/src/hooks/queries/rank/useReviewRankingQuery.ts delete mode 100644 frontend/src/hooks/queries/recipe/index.ts delete mode 100644 frontend/src/hooks/queries/recipe/useInfiniteRecipeCommentQuery.ts delete mode 100644 frontend/src/hooks/queries/recipe/useInfiniteRecipesQuery.ts delete mode 100644 frontend/src/hooks/queries/recipe/useRecipeCommentMutation.ts delete mode 100644 frontend/src/hooks/queries/recipe/useRecipeDetailQuery.ts delete mode 100644 frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts delete mode 100644 frontend/src/hooks/queries/recipe/useRecipeRegisterFormMutation.ts delete mode 100644 frontend/src/hooks/queries/review/index.ts delete mode 100644 frontend/src/hooks/queries/review/useReviewDetailQuery.ts delete mode 100644 frontend/src/hooks/queries/review/useReviewFavoriteMutation.ts delete mode 100644 frontend/src/hooks/queries/review/useReviewRegisterFormMutation.ts delete mode 100644 frontend/src/hooks/queries/review/useReviewTagsQuery.ts delete mode 100644 frontend/src/hooks/queries/search/index.ts delete mode 100644 frontend/src/hooks/queries/search/useInfiniteProductSearchAutocompleteQuery.ts delete mode 100644 frontend/src/hooks/queries/search/useInfiniteProductSearchResultsQuery.ts delete mode 100644 frontend/src/hooks/queries/search/useInfiniteRecipeSearchResultsQuery.ts delete mode 100644 frontend/src/hooks/queries/useSuspendedInfiniteQuery.ts delete mode 100644 frontend/src/hooks/queries/useSuspendedQuery.ts delete mode 100644 frontend/src/hooks/review/index.ts delete mode 100644 frontend/src/hooks/review/useDisplayTag.ts delete mode 100644 frontend/src/hooks/review/useStarRatingHover.ts delete mode 100644 frontend/src/hooks/search/index.ts delete mode 100644 frontend/src/hooks/search/useSearch.ts delete mode 100644 frontend/src/index.tsx delete mode 100644 frontend/src/mocks/browser.ts delete mode 100644 frontend/src/mocks/data/banners.json delete mode 100644 frontend/src/mocks/data/comments.json delete mode 100644 frontend/src/mocks/data/foodCategory.json delete mode 100644 frontend/src/mocks/data/memberRecipes.json delete mode 100644 frontend/src/mocks/data/memberReviews.json delete mode 100644 frontend/src/mocks/data/members.json delete mode 100644 frontend/src/mocks/data/pbProducts.json delete mode 100644 frontend/src/mocks/data/productDetail.json delete mode 100644 frontend/src/mocks/data/productDetails.json delete mode 100644 frontend/src/mocks/data/productRankingList.json delete mode 100644 frontend/src/mocks/data/productSearchResults.json delete mode 100644 frontend/src/mocks/data/products.json delete mode 100644 frontend/src/mocks/data/recipeDetail.json delete mode 100644 frontend/src/mocks/data/recipeRankingList.json delete mode 100644 frontend/src/mocks/data/recipes.json delete mode 100644 frontend/src/mocks/data/reviewDetail.json delete mode 100644 frontend/src/mocks/data/reviewRankingList.json delete mode 100644 frontend/src/mocks/data/reviewTagList.json delete mode 100644 frontend/src/mocks/data/reviews.json delete mode 100644 frontend/src/mocks/data/searchingProducts.json delete mode 100644 frontend/src/mocks/data/storeCategory.json delete mode 100644 frontend/src/mocks/handlers/bannerHandlers.ts delete mode 100644 frontend/src/mocks/handlers/index.ts delete mode 100644 frontend/src/mocks/handlers/loginHandlers.ts delete mode 100644 frontend/src/mocks/handlers/logoutHandlers.ts delete mode 100644 frontend/src/mocks/handlers/memberHandlers.ts delete mode 100644 frontend/src/mocks/handlers/productHandlers.ts delete mode 100644 frontend/src/mocks/handlers/rankingHandlers.ts delete mode 100644 frontend/src/mocks/handlers/recipeHandlers.ts delete mode 100644 frontend/src/mocks/handlers/reviewHandlers.ts delete mode 100644 frontend/src/mocks/handlers/searchHandlers.ts delete mode 100644 frontend/src/mocks/handlers/utils.ts delete mode 100644 frontend/src/pages/AuthPage.tsx delete mode 100644 frontend/src/pages/HomePage.tsx delete mode 100644 frontend/src/pages/IntegratedSearchPage.tsx delete mode 100644 frontend/src/pages/LoginPage.tsx delete mode 100644 frontend/src/pages/MemberModifyPage.tsx delete mode 100644 frontend/src/pages/MemberPage.tsx delete mode 100644 frontend/src/pages/MemberRecipePage.tsx delete mode 100644 frontend/src/pages/MemberReviewPage.tsx delete mode 100644 frontend/src/pages/NotFoundPage.tsx delete mode 100644 frontend/src/pages/ProductDetailPage.tsx delete mode 100644 frontend/src/pages/ProductListPage.tsx delete mode 100644 frontend/src/pages/RecipeDetailPage.tsx delete mode 100644 frontend/src/pages/RecipePage.tsx delete mode 100644 frontend/src/pages/ReviewDetailPage.tsx delete mode 100644 frontend/src/pages/SearchPage.tsx delete mode 100644 frontend/src/router/App.tsx delete mode 100644 frontend/src/router/index.tsx delete mode 100644 frontend/src/service/channelTalk.ts delete mode 100644 frontend/src/styles/animations.ts delete mode 100644 frontend/src/styles/font.ts delete mode 100644 frontend/src/styles/globalStyle.ts delete mode 100644 frontend/src/types/banner.ts delete mode 100644 frontend/src/types/common.ts delete mode 100644 frontend/src/types/images.d.ts delete mode 100644 frontend/src/types/member.ts delete mode 100644 frontend/src/types/product.ts delete mode 100644 frontend/src/types/ranking.ts delete mode 100644 frontend/src/types/recipe.ts delete mode 100644 frontend/src/types/response.ts delete mode 100644 frontend/src/types/review.ts delete mode 100644 frontend/src/types/search.ts delete mode 100644 frontend/src/types/styled.d.ts delete mode 100644 frontend/src/utils/category.ts delete mode 100644 frontend/src/utils/convertTagColor.ts delete mode 100644 frontend/src/utils/date.ts delete mode 100644 frontend/src/utils/displaySlice.ts delete mode 100644 frontend/src/utils/localStorage.ts delete mode 100644 frontend/src/utils/uuid.ts delete mode 100644 frontend/tsconfig.json delete mode 100644 frontend/webpack.common.js delete mode 100644 frontend/webpack.dev.js delete mode 100644 frontend/webpack.prod.js delete mode 100644 frontend/yarn.lock rename {backend/gradle => gradle}/wrapper/gradle-wrapper.jar (100%) rename {backend/gradle => gradle}/wrapper/gradle-wrapper.properties (100%) rename backend/gradlew => gradlew (100%) rename backend/gradlew.bat => gradlew.bat (100%) rename backend/settings.gradle => settings.gradle (100%) rename {backend/src => src}/main/java/com/funeat/FuneatApplication.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/application/AdminChecker.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/application/AdminService.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/domain/AdminAuthInfo.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/AdminCategoryResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/AdminProductResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/AdminProductSearchResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/AdminReviewResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/AdminReviewSearchResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/ProductCreateRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/ProductSearchCondition.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/ProductUpdateRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/dto/ReviewSearchCondition.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/presentation/AdminController.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/presentation/AdminLoginController.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/repository/AdminProductRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/repository/AdminReviewRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/specification/AdminProductSpecification.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/specification/AdminReviewSpecification.java (100%) rename {backend/src => src}/main/java/com/funeat/admin/util/AdminCheckInterceptor.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/application/AuthService.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/KakaoTokenDto.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/KakaoUserInfoDto.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/LoginInfo.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/SignUserDto.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/TokenResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/dto/UserInfoDto.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/exception/AuthErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/exception/AuthException.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/presentation/AuthApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/presentation/AuthController.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/util/AuthArgumentResolver.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/util/AuthHandlerInterceptor.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/util/AuthenticationPrincipal.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/util/KakaoPlatformUserProvider.java (100%) rename {backend/src => src}/main/java/com/funeat/auth/util/PlatformUserProvider.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/application/BannerService.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/domain/Banner.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/dto/BannerResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/persistence/BannerRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/presentation/BannerApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/banner/presentation/BannerController.java (100%) rename {backend/src => src}/main/java/com/funeat/comment/domain/Comment.java (100%) rename {backend/src => src}/main/java/com/funeat/comment/persistence/CommentRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/comment/specification/CommentSpecification.java (100%) rename {backend/src => src}/main/java/com/funeat/common/CustomPageableHandlerMethodArgumentResolver.java (100%) rename {backend/src => src}/main/java/com/funeat/common/ImageUploader.java (100%) rename {backend/src => src}/main/java/com/funeat/common/OpenApiConfig.java (100%) rename {backend/src => src}/main/java/com/funeat/common/StringToCategoryTypeConverter.java (100%) rename {backend/src => src}/main/java/com/funeat/common/WebConfig.java (100%) rename {backend/src => src}/main/java/com/funeat/common/dto/PageDto.java (100%) rename {backend/src => src}/main/java/com/funeat/common/exception/CommonException.java (100%) rename {backend/src => src}/main/java/com/funeat/common/logging/Logging.java (100%) rename {backend/src => src}/main/java/com/funeat/common/logging/LoggingAspect.java (100%) rename {backend/src => src}/main/java/com/funeat/common/repository/BaseRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/common/repository/BaseRepositoryImpl.java (100%) rename {backend/src => src}/main/java/com/funeat/common/s3/AwsConfig.java (100%) rename {backend/src => src}/main/java/com/funeat/common/s3/S3Uploader.java (100%) rename {backend/src => src}/main/java/com/funeat/exception/CommonErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/exception/ErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/exception/GlobalException.java (100%) rename {backend/src => src}/main/java/com/funeat/exception/presentation/GlobalControllerAdvice.java (100%) rename {backend/src => src}/main/java/com/funeat/member/application/MemberService.java (100%) rename {backend/src => src}/main/java/com/funeat/member/domain/Member.java (100%) rename {backend/src => src}/main/java/com/funeat/member/domain/favorite/RecipeFavorite.java (100%) rename {backend/src => src}/main/java/com/funeat/member/domain/favorite/ReviewFavorite.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberProfileResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberRecipeDto.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberRecipeProductDto.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberRecipesResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberReviewDto.java (100%) rename {backend/src => src}/main/java/com/funeat/member/dto/MemberReviewsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/member/exception/MemberErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/member/exception/MemberException.java (100%) rename {backend/src => src}/main/java/com/funeat/member/persistence/MemberRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/member/persistence/RecipeFavoriteRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/member/persistence/ReviewFavoriteRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/member/presentation/MemberApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/member/presentation/MemberController.java (100%) rename {backend/src => src}/main/java/com/funeat/product/application/CategoryService.java (100%) rename {backend/src => src}/main/java/com/funeat/product/application/ProductService.java (100%) rename {backend/src => src}/main/java/com/funeat/product/domain/Category.java (100%) rename {backend/src => src}/main/java/com/funeat/product/domain/CategoryType.java (100%) rename {backend/src => src}/main/java/com/funeat/product/domain/Product.java (100%) rename {backend/src => src}/main/java/com/funeat/product/domain/ProductRecipe.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/CategoryResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/ProductInCategoryDto.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/ProductResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/ProductReviewCountDto.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/ProductSortCondition.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/ProductsInCategoryResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/RankingProductDto.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/RankingProductsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/SearchProductDto.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/SearchProductResultDto.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/SearchProductResultsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/dto/SearchProductsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/product/exception/CategoryErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/product/exception/CategoryException.java (100%) rename {backend/src => src}/main/java/com/funeat/product/exception/ProductErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/product/exception/ProductException.java (100%) rename {backend/src => src}/main/java/com/funeat/product/persistence/CategoryRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/product/persistence/ProductRecipeRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/product/persistence/ProductRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/product/persistence/ProductSpecification.java (100%) rename {backend/src => src}/main/java/com/funeat/product/presentation/CategoryApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/product/presentation/CategoryController.java (100%) rename {backend/src => src}/main/java/com/funeat/product/presentation/ProductApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/product/presentation/ProductController.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/application/RecipeService.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/domain/Recipe.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/domain/RecipeImage.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/ProductRecipeDto.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RankingRecipeDto.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RankingRecipesResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeAuthorDto.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCommentCondition.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCommentCreateRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCommentMemberResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCommentResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCommentsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeCreateRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeDetailResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeDto.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/RecipeFavoriteRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/SearchRecipeResultDto.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/SearchRecipeResultsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/dto/SortingRecipesResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/exception/RecipeErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/exception/RecipeException.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/persistence/RecipeImageRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/persistence/RecipeRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/presentation/RecipeApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/presentation/RecipeController.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/util/RecipeDetailHandlerInterceptor.java (100%) rename {backend/src => src}/main/java/com/funeat/recipe/util/RecipeHandlerInterceptor.java (100%) rename {backend/src => src}/main/java/com/funeat/review/application/ReviewDeleteEvent.java (100%) rename {backend/src => src}/main/java/com/funeat/review/application/ReviewDeleteEventListener.java (100%) rename {backend/src => src}/main/java/com/funeat/review/application/ReviewService.java (100%) rename {backend/src => src}/main/java/com/funeat/review/domain/Review.java (100%) rename {backend/src => src}/main/java/com/funeat/review/domain/ReviewTag.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/MostFavoriteReviewResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/RankingReviewDto.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/RankingReviewsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/ReviewCreateRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/ReviewDetailResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/ReviewFavoriteRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/SortingReviewDto.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/SortingReviewRequest.java (100%) rename {backend/src => src}/main/java/com/funeat/review/dto/SortingReviewsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/review/exception/ReviewErrorCode.java (100%) rename {backend/src => src}/main/java/com/funeat/review/exception/ReviewException.java (100%) rename {backend/src => src}/main/java/com/funeat/review/persistence/ReviewCustomRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/review/persistence/ReviewRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java (100%) rename {backend/src => src}/main/java/com/funeat/review/persistence/ReviewTagRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/review/presentation/ReviewApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/review/presentation/ReviewController.java (100%) rename {backend/src => src}/main/java/com/funeat/review/specification/LongTypeReviewSortSpec.java (100%) rename {backend/src => src}/main/java/com/funeat/review/specification/SortingReviewSpecification.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/application/TagService.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/domain/Tag.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/domain/TagType.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/dto/TagDto.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/dto/TagsResponse.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/persistence/TagRepository.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/presentation/TagApiController.java (100%) rename {backend/src => src}/main/java/com/funeat/tag/presentation/TagController.java (100%) rename {backend/src => src}/main/resources/application-dev.yml (100%) rename {backend/src => src}/main/resources/application-local.yml (100%) rename {backend/src => src}/main/resources/application-prod.yml (100%) rename {backend/src => src}/main/resources/application.yml (100%) rename {backend/src => src}/main/resources/logback-spring-dev.xml (100%) rename {backend/src => src}/main/resources/logback-spring-prod.xml (100%) rename {backend/src => src}/main/resources/logback-spring.xml (100%) rename {backend/src => src}/main/resources/logback-variables.yml (100%) rename {backend/src => src}/test/java/com/funeat/FuneatApplicationTests.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/auth/AuthAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/auth/LoginSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/banner/BannerAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/banner/BannerSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/common/AcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/common/CommonSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/common/TestPlatformUserProvider.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/member/MemberSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/product/CategoryAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/product/CategorySteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/product/ProductSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/recipe/RecipeAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/recipe/RecipeSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/review/ReviewAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/review/ReviewSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/tag/TagAcceptanceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/acceptance/tag/TagSteps.java (100%) rename {backend/src => src}/test/java/com/funeat/auth/application/AuthServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/banner/application/BannerServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/banner/persistence/BannerRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/common/DataCleaner.java (100%) rename {backend/src => src}/test/java/com/funeat/common/DataClearExtension.java (100%) rename {backend/src => src}/test/java/com/funeat/common/EventTest.java (100%) rename {backend/src => src}/test/java/com/funeat/common/RepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/common/ServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/common/TestImageUploader.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/BannerFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/CategoryFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/ImageFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/MemberFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/PageFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/ProductFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/RecipeFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/ReviewFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/ScoreFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/fixture/TagFixture.java (100%) rename {backend/src => src}/test/java/com/funeat/member/application/MemberServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/member/application/TestMemberService.java (100%) rename {backend/src => src}/test/java/com/funeat/member/domain/MemberTest.java (100%) rename {backend/src => src}/test/java/com/funeat/member/domain/favorite/RecipeFavoriteTest.java (100%) rename {backend/src => src}/test/java/com/funeat/member/persistence/MemberRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/member/persistence/RecipeFavoriteRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/member/persistence/ReviewFavoriteRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/application/ProductServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/domain/ProductTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/domain/favorite/ReviewFavoriteTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/persistence/CategoryRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/persistence/ProductRecipeRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/product/persistence/ProductRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/recipe/application/RecipeServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/recipe/domain/RecipeTest.java (100%) rename {backend/src => src}/test/java/com/funeat/recipe/persistence/RecipeImageRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/review/application/ReviewDeleteEventListenerTest.java (100%) rename {backend/src => src}/test/java/com/funeat/review/application/ReviewServiceTest.java (100%) rename {backend/src => src}/test/java/com/funeat/review/domain/ReviewTest.java (100%) rename {backend/src => src}/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/review/persistence/ReviewTagRepositoryTest.java (100%) rename {backend/src => src}/test/java/com/funeat/tag/persistence/TagRepositoryTest.java (100%) rename {backend/src => src}/test/resources/application.yml (100%) diff --git a/backend/.gitignore b/.gitignore similarity index 100% rename from backend/.gitignore rename to .gitignore diff --git a/README.md b/README.md deleted file mode 100644 index a3819192..00000000 --- a/README.md +++ /dev/null @@ -1,107 +0,0 @@ -
- -
- - - -
-
- -궁금해? 맛있을걸? 먹어봐!
-🍙 편의점 음식 리뷰 & 꿀조합 공유 서비스 🍙
- -
- -[![Application](http://img.shields.io/badge/funeat.site-D8EAFF?style=for-the-badge&logo=aHR0cHM6Ly9naXRodWIuY29tL3dvb3dhY291cnNlLXRlYW1zLzIwMjMtZnVuLWVhdC9hc3NldHMvODA0NjQ5NjEvOWI1OWY3NzktY2M5MS00MTJhLWE3NDUtZGQ3M2IzY2UxZGNk&logoColor=black&link=https://funeat.site/)](https://funeat.site/) -[![WIKI](http://img.shields.io/badge/-GitHub%20WiKi-FFEC99?style=for-the-badge&logoColor=black&link=https://github.com/woowacourse-teams/2023-fun-eat/wiki)](https://github.com/woowacourse-teams/2023-fun-eat/wiki) -[![Release](https://img.shields.io/github/v/release/woowacourse-teams/2023-fun-eat?style=for-the-badge&color=FFCFCF)](https://github.com/woowacourse-teams/2023-fun-eat/releases/tag/v1.3.0) - -
- -
- -# 🥄 서비스 소개 - -![1_메인페이지](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/9663f7b5-cd38-4f06-86fb-c6636fc364c6) - -
- -## 1. 편의점마다 특색있는 음식 궁금해? - -![5_상품목록](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/03fb9955-61fa-4228-a270-ce9dffc710c6) -![6_상품상세](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/694bc8db-74bd-4fa1-b499-900cd27f5028) -![4_검색](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/6a157e08-79d8-450b-9511-ffa461000a22) - -
-
- -## 2. 솔직한 리뷰를 보면 더 맛있을걸? - -![2_리뷰](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/4bf5ecd7-df08-45d0-b592-8629f3a4e3e6) - -
-
- -## 3. 생각지 못했던 꿀조합, 먹어봐! - -![3_꿀조합](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/8e560b40-d039-47ce-ad29-5e244cba4bf2) - -
-
- -# 🛠️ 기술 스택 - -### 백엔드 - -
- BE_기술스택 -
- -
- -### 프론트엔드 - -
- FE_기술스택 -
- -
- -### 인프라 - -
- 인프라_기술스택 -
- -
-
- -# 인프라 구조 - -### CI/CD - -
- cicd -
- -### 구조 - -
- 인프라 구조 -
- -
-
- -# 👨‍👨‍👧‍👧👩‍👦‍👦 팀원 - -| Frontend | Frontend | Frontend | Backend | Backend | Backend | Backend | -| :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | -| 타미 | 해온 | 황펭 | 로건 | 망고 | 오잉 | 우가 | -| [🐰 타미](https://github.com/xodms0309) | [🌞 해온](https://github.com/hae-on) | [🐧 황펭](https://github.com/Leejin-Yang) | [😺 로건](https://github.com/70825) | [🥭 망고](https://github.com/Go-Jaecheol) | [👻 오잉](https://github.com/hanueleee) | [🍖 우가](https://github.com/wugawuga) | - -
- -
- 팀소개 -
diff --git a/backend/build.gradle b/build.gradle similarity index 100% rename from backend/build.gradle rename to build.gradle diff --git a/frontend/.babelrc.json b/frontend/.babelrc.json deleted file mode 100644 index 2c055d07..00000000 --- a/frontend/.babelrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "sourceType": "unambiguous", - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "chrome": 100 - } - } - ], - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": ["babel-plugin-styled-components"] -} diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index 963fcc6c..00000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,113 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - 'plugin:storybook/recommended', - 'plugin:import/recommended', - ], - ignorePatterns: ['*.js'], - overrides: [ - { - env: { - node: true, - }, - files: ['.eslintrc.{js,cjs}'], - parserOptions: { - sourceType: 'script', - }, - }, - { - env: { - jest: true, - }, - files: ['__tests__/**/*.{ts,tsx}'], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - }, - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - plugins: ['@typescript-eslint', 'react', 'import'], - rules: { - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-var-requires': 0, - '@typescript-eslint/consistent-type-imports': [ - 'error', - { - prefer: 'type-imports', - disallowTypeAnnotations: false, - }, - ], - 'react/jsx-key': [ - 'error', - { - warnOnDuplicates: true, - }, - ], - 'react/self-closing-comp': [ - 'error', - { - component: true, - html: true, - }, - ], - 'import/order': [ - 'error', - { - groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index'], 'object', 'unknown'], - pathGroups: [ - { - pattern: '@storybook/**', - group: 'external', - }, - { - pattern: '@fun-eat/**', - group: 'external', - }, - { - pattern: '@tanstack/**', - group: 'external', - }, - { - pattern: '@*/**', - group: 'unknown', - }, - { - pattern: '@*', - group: 'unknown', - }, - ], - pathGroupsExcludedImportTypes: ['unknown'], - alphabetize: { - order: 'asc', - caseInsensitive: true, - }, - 'newlines-between': 'always', - }, - ], - 'import/no-unresolved': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/ban-types': 'off', - 'import/export': 'off', - }, - settings: { - 'import/resolver': { - typescript: {}, - webpack: {}, - }, - }, -}; diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 8a2a134c..00000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules -dist -.DS_Store -.AppleDouble -.LSOverride -.env -coverage -test-results -junit.xml diff --git a/frontend/.nvmrc b/frontend/.nvmrc deleted file mode 100644 index 3876fd49..00000000 --- a/frontend/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.16.1 diff --git a/frontend/.prettierrc b/frontend/.prettierrc deleted file mode 100644 index 8c2d4fe2..00000000 --- a/frontend/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "printWidth": 120, - "singleQuote": true, - "endOfLine": "auto", - "semi": true, - "tabWidth": 2 -} diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts deleted file mode 100644 index 8791c62d..00000000 --- a/frontend/.storybook/main.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { StorybookConfig } from '@storybook/react-webpack5'; -import path from 'path'; - -const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/addon-interactions', - 'msw-storybook-addon', - '@storybook/addon-onboarding', - ], - framework: { - name: '@storybook/react-webpack5', - options: {}, - }, - core: { - builder: { - name: '@storybook/builder-webpack5', - options: { - fsCache: true, - lazyCompilation: true, - }, - }, - }, - webpackFinal: async (config) => { - if (config.resolve) { - config.resolve.alias = { - ...config.resolve.alias, - '@': path.resolve(__dirname, '../src'), - '@apis': path.resolve(__dirname, '../src/apis'), - '@assets': path.resolve(__dirname, '../src/assets'), - '@components': path.resolve(__dirname, '../src/components'), - '@constants': path.resolve(__dirname, '../src/constants'), - '@hooks': path.resolve(__dirname, '../src/hooks'), - '@mocks': path.resolve(__dirname, '../src/mocks'), - '@pages': path.resolve(__dirname, '../src/pages'), - '@router': path.resolve(__dirname, '../src/router'), - '@styles': path.resolve(__dirname, '../src/styles'), - '@utils': path.resolve(__dirname, '../src/utils'), - }; - } - const imageRule = config.module?.rules?.find((rule) => { - const test = (rule as { test: RegExp }).test; - - if (!test) return false; - - return test.test('.svg'); - }) as { [key: string]: any }; - - imageRule.exclude = /\.svg$/; - - config.module?.rules?.push({ - test: /\.svg$/, - use: ['@svgr/webpack'], - }); - - return config; - }, - docs: { - autodocs: true, - }, - staticDirs: ['../public'], -}; -export default config; diff --git a/frontend/.storybook/preview-body.html b/frontend/.storybook/preview-body.html deleted file mode 100644 index a37b26cb..00000000 --- a/frontend/.storybook/preview-body.html +++ /dev/null @@ -1,129 +0,0 @@ - -
-
- diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx deleted file mode 100644 index 944ee1c0..00000000 --- a/frontend/.storybook/preview.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { FunEatProvider } from '@fun-eat/design-system'; -import type { Preview } from '@storybook/react'; -import { initialize, mswDecorator } from 'msw-storybook-addon'; -import { - loginHandlers, - productHandlers, - reviewHandlers, - rankingHandlers, - memberHandlers, - recipeHandlers, - searchHandlers, -} from '../src/mocks/handlers'; -import { BrowserRouter } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -initialize({ - serviceWorker: { - url: '/mockServiceWorker.js', - }, -}); - -const queryClient = new QueryClient(); - -export const decorators = [ - (Story) => ( - - - - - - - - ), - mswDecorator, -]; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - msw: { - handlers: [ - ...productHandlers, - ...reviewHandlers, - ...loginHandlers, - ...rankingHandlers, - ...memberHandlers, - ...recipeHandlers, - ...searchHandlers, - ], - }, - }, -}; - -export default preview; diff --git a/frontend/.stylelintrc.js b/frontend/.stylelintrc.js deleted file mode 100644 index 914c886f..00000000 --- a/frontend/.stylelintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -const { propertyOrdering, selectorOrdering } = require('stylelint-semantic-groups'); - -propertyOrdering[0] = propertyOrdering[0].map((rule) => { - rule.emptyLineBefore = 'never'; - return rule; -}); - -module.exports = { - plugins: ['stylelint-order'], - customSyntax: 'postcss-styled-syntax', - rules: { - 'order/order': selectorOrdering, - 'order/properties-order': propertyOrdering, - }, -}; diff --git a/frontend/__tests__/hooks/useImageUploader.test.ts b/frontend/__tests__/hooks/useImageUploader.test.ts deleted file mode 100644 index 3de2da05..00000000 --- a/frontend/__tests__/hooks/useImageUploader.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useImageUploader } from '@/hooks/common'; -import { renderHook, act } from '@testing-library/react'; - -const originalCreateObjectUrl = URL.createObjectURL; -const originalRevokeObjectUrl = URL.revokeObjectURL; - -beforeAll(() => { - URL.createObjectURL = jest.fn(() => 'mocked url'); - URL.revokeObjectURL = jest.fn(); -}); - -afterAll(() => { - URL.createObjectURL = originalCreateObjectUrl; - URL.revokeObjectURL = originalRevokeObjectUrl; -}); - -it('uploadImage를 사용하여 이미지 파일을 업로드할 수 있다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.png', { type: 'image/png' }); - - act(() => { - result.current.uploadImage(file); - }); - - expect(result.current.imageFile).toBe(file); - expect(result.current.previewImage).toBe('mocked url'); - expect(URL.createObjectURL).toHaveBeenCalledWith(file); -}); - -it('이미지 파일이 아니면 "이미지 파일만 업로드 가능합니다." 메시지를 보여주는 alert 창이 뜬다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.txt', { type: 'text/plain' }); - - global.alert = jest.fn(); - - act(() => { - result.current.uploadImage(file); - }); - - expect(global.alert).toHaveBeenCalledWith('이미지 파일만 업로드 가능합니다.'); -}); - -it('deleteImage를 사용하여 이미지 파일을 삭제할 수 있다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.png', { type: 'image/png' }); - - act(() => { - result.current.uploadImage(file); - }); - - act(() => { - result.current.deleteImage(); - }); - - expect(result.current.imageFile).toBeNull(); - expect(result.current.previewImage).toBe(''); - expect(URL.revokeObjectURL).toHaveBeenCalledWith('mocked url'); -}); diff --git a/frontend/__tests__/hooks/useStarRating.test.ts b/frontend/__tests__/hooks/useStarRating.test.ts deleted file mode 100644 index 66ed60cb..00000000 --- a/frontend/__tests__/hooks/useStarRating.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useStarRatingHover } from '@/hooks/review'; -import { renderHook, act } from '@testing-library/react'; - -it('handleMouseEnter를 사용하여 마우스 호버된 별점 값을 저장할 수 있다.', () => { - const { result } = renderHook(() => useStarRatingHover()); - - expect(result.current.hovering).toBe(0); - - act(() => { - result.current.handleMouseEnter(3); - }); - - expect(result.current.hovering).toBe(3); -}); - -it('handleMouseLeave를 사용하여 마우스 호버된 별점을 초기화 할 수 있다.', () => { - const { result } = renderHook(() => useStarRatingHover()); - - expect(result.current.hovering).toBe(0); - - act(() => { - result.current.handleMouseEnter(3); - }); - - expect(result.current.hovering).toBe(3); - - act(() => { - result.current.handleMouseLeave(); - }); - - expect(result.current.hovering).toBe(0); -}); diff --git a/frontend/__tests__/hooks/useTabMenu.test.ts b/frontend/__tests__/hooks/useTabMenu.test.ts deleted file mode 100644 index d8ca5317..00000000 --- a/frontend/__tests__/hooks/useTabMenu.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useTabMenu } from '@/hooks/common'; -import { renderHook, act } from '@testing-library/react'; - -it('선택된 탭 초기 상태는 0번 인덱스이다.', () => { - const { result } = renderHook(() => useTabMenu()); - - expect(result.current.selectedTabMenu).toBe(0); - expect(result.current.isFirstTabMenu).toBe(true); -}); - -it('handleTabMenuClick를 사용하여 선택한 탭 인덱스를 저장할 수 있다. ', () => { - const { result } = renderHook(() => useTabMenu()); - - act(() => { - result.current.handleTabMenuClick(1); - }); - - expect(result.current.selectedTabMenu).toBe(1); -}); - -it('initTabMenu를 사용하여 선택된 탭을 맨 처음 탭으로 초기화할 수 있다.', () => { - const { result } = renderHook(() => useTabMenu()); - - act(() => { - result.current.handleTabMenuClick(1); - result.current.initTabMenu(); - }); - - expect(result.current.selectedTabMenu).toBe(0); -}); diff --git a/frontend/jest.config.js b/frontend/jest.config.js deleted file mode 100644 index 82a71339..00000000 --- a/frontend/jest.config.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - testEnvironment: 'jsdom', - transform: { - '^.+\\.(js|ts|tsx)?$': 'ts-jest', - }, - moduleNameMapper: { - '^@/(.*)$': '/src/$1', - }, - testMatch: ['/__tests__/**/*.test.(js|jsx|ts|tsx)'], - reporters: [ - 'default', - [ - 'jest-junit', - { - outputDirectory: '/test-results', - outputName: 'results.xml', - }, - ], - ], -}; diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 33515820..00000000 --- a/frontend/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "fun-eat", - "version": "0.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "start": "webpack serve --open --config webpack.dev.js", - "build": "webpack --config webpack.prod.js", - "build-dev": "webpack --config webpack.dev.js", - "storybook": "storybook dev -p 6006", - "lint:styled": "stylelint './src/**/*.tsx' --fix", - "test": "jest", - "test:coverage": "jest --watchAll --coverage" - }, - "dependencies": { - "@fun-eat/design-system": "^0.4.1", - "@tanstack/react-query": "^4.32.6", - "@tanstack/react-query-devtools": "^4.32.6", - "browser-image-compression": "^2.0.2", - "dayjs": "^1.11.9", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-ga4": "^2.1.0", - "react-router-dom": "^6.14.2", - "styled-components": "^6.0.2" - }, - "devDependencies": { - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@storybook/addon-essentials": "^7.0.27", - "@storybook/addon-interactions": "^7.0.27", - "@storybook/addon-links": "^7.0.27", - "@storybook/addon-onboarding": "^1.0.8", - "@storybook/blocks": "^7.0.27", - "@storybook/react": "^7.0.27", - "@storybook/react-webpack5": "^7.0.27", - "@storybook/testing-library": "^0.0.14-next.2", - "@svgr/webpack": "^8.0.1", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.3", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", - "@types/styled-components": "^5.1.26", - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", - "babel-plugin-styled-components": "^2.1.4", - "copy-webpack-plugin": "^11.0.0", - "dotenv-webpack": "^8.0.1", - "eslint": "^8.44.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-import-resolver-webpack": "^0.13.2", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-storybook": "^0.6.12", - "html-webpack-plugin": "^5.5.3", - "jest": "^29.6.2", - "jest-environment-jsdom": "^29.6.2", - "jest-junit": "^16.0.0", - "msw": "^1.2.3", - "msw-storybook-addon": "^1.8.0", - "postcss": "^8.4.29", - "postcss-styled-syntax": "^0.4.0", - "prettier": "^2.8.8", - "storybook": "^7.1.1", - "stylelint": "^15.10.3", - "stylelint-order": "^6.0.3", - "stylelint-semantic-groups": "^1.2.0", - "ts-jest": "^29.1.1", - "ts-loader": "^9.4.4", - "typescript": "^5.1.6", - "webpack": "^5.88.1", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" - }, - "msw": { - "workerDirectory": "public" - }, - "resolutions": { - "jackspeak": "2.1.1" - } -} diff --git a/frontend/public/assets/apple-icon-180x180.png b/frontend/public/assets/apple-icon-180x180.png deleted file mode 100644 index 986c9be845d058ef23958389157d3dd7000d2dc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13932 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 diff --git a/frontend/public/assets/favicon-16x16.png b/frontend/public/assets/favicon-16x16.png deleted file mode 100644 index a277e1839cdc36cd6ab222050a38b0217cac24a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1448 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>pEST@^=?*kOUQ}2?Ot`nB?c1{#ug_mP zck*OQV}lq!FHlfPLe$I2fsKIys0PSZmJkIBva>MD3JWTUivW>=xbXk~|A8h0ow4D; z7c&!6YeUuVALjo0JRc|mL?Lm>Hb$zqE_eO^xoE?~FALAy%}aF!Y6*%>k`ocqR#OEU zD8R@NlU=ARDGKyp|JEa=y)%@hBox8s14%eTMM|uu*sq}?5NOe+C*PYEZxH3-lo1k; z5f%g^0TFKQ9h*x3|6Kh4&q|=%j5Jgyw6~qxw|~dxO+c3`iU})-3h5}xYb(h5xjN68 zK6UoAss3&*N@Bu+9&Y#V-UYhq;e-1jKAs99f?D#j+c$6e^XJdMfB$xG-=?h~=jP=j zC(Qr)@w4ZTpZxs!JYKd+f;JlgEx0A1J+G z2aGbFfjX@0saJpgc`-qPt@hh$;kYmVnweCty)b8FG^v@sB2~Zk@2}sFJ#Ef+ur<`> zXUgy2E~UL=&B+`09^5{&c9RCH4L6^Jth}@UuPduVqQz05J&Z}-?k=YHw%yDIaySb- zB8wRqbi6^BajEUJl|VuE5>H=O_U9Y|EDGuqs+U{@3O)36aSV~ToSblgO+t0)lGK!^ zGZJdjDp_4sQ$<5f#lj5c`^GvO+bSDtM|;nfT{F+w-CW&V-+aG=|NVx^jg8&6FKDRm z-LPWE5|iUMj$G+Ec;}Fj?e;Z$7A@MOWq$Y6t+s>L&h6W^Y}>j;`_?TqyngUv=fRsx zSMJ=fbnCjcdlwt8KYg{g@%Y}|%eSvzv~T~S_2&A=o2NIPFFtg{bMkVZxnb6|Zhn2@ z_RS63&!35`-2D8^TyyQ(ee35dG*8%2?~o8s;E@qg;gT|?LxZ_FqQoaBrp75LsK_g8 zmc=G3h!0&IPKK0I3bhD9uf>vU15!F3nBNE3vW)04gnJFkJqBw?3L0 zPmn2ARvxLD=@}&qhL%PSv4x>PHR4F-K-Gk2rj%rWlw4k;$N^L$iKN6gGdH!QvLKZK z>?-|&ymb3bVSZ@l_=W&gF&LUz8X1}y7@Hc!|EUN8D&a>mCpfbzmBGNtY04tIH$bT{ zB&pCKPlmM2oK&FK^>gz|a`Myl(-O-vlk@Y6^$|g?Z91S9|e diff --git a/frontend/public/assets/favicon-32x32.png b/frontend/public/assets/favicon-32x32.png deleted file mode 100644 index 89b5dff577d1adf1fead02e8b0f701c8a5325c6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2349 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 diff --git a/frontend/public/assets/og-image.png b/frontend/public/assets/og-image.png deleted file mode 100644 index 3d4b525e2bc61113de6df4efa9ffd2e175c5c5b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20533 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 diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index 36782ea8..00000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - 펀잇 - - - - - - - - - - - - - - - - -
-
-
- - diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 7e44fe2f..00000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "펀잇", - "short_name": "펀잇", - "description": "궁금해? 맛있을걸? 먹어봐 🥄", - "display": "standalone", - "icons": [ - { - "src": "/assets/favicon-16x16.png", - "sizes": "16x16", - "type": "image/png" - }, - { - "src": "/assets/favicon-32x32.png", - "sizes": "32x32", - "type": "image/png" - }, - { - "src": "/assets/apple-icon-180x180.png", - "sizes": "180x180", - "type": "image/png" - } - ] -} diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js deleted file mode 100644 index 51d85eee..00000000 --- a/frontend/public/mockServiceWorker.js +++ /dev/null @@ -1,303 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker (1.3.2). - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - * - Please do NOT serve this file on production. - */ - -const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' -const activeClientIds = new Set() - -self.addEventListener('install', function () { - self.skipWaiting() -}) - -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -self.addEventListener('message', async function (event) { - const clientId = event.source.id - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: INTEGRITY_CHECKSUM, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: true, - }) - break - } - - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -self.addEventListener('fetch', function (event) { - const { request } = event - const accept = request.headers.get('accept') || '' - - // Bypass server-sent events. - if (accept.includes('text/event-stream')) { - return - } - - // Bypass navigation requests. - if (request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) - - event.respondWith( - handleRequest(event, requestId).catch((error) => { - if (error.name === 'NetworkError') { - console.warn( - '[MSW] Successfully emulated a network error for the "%s %s" request.', - request.method, - request.url, - ) - return - } - - // At this point, any exception indicates an issue with the original request/response. - console.error( - `\ -[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, - request.method, - request.url, - `${error.name}: ${error.message}`, - ) - }), - ) -}) - -async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - ;(async function () { - const clonedResponse = response.clone() - sendToClient(client, { - type: 'RESPONSE', - payload: { - requestId, - type: clonedResponse.type, - ok: clonedResponse.ok, - status: clonedResponse.status, - statusText: clonedResponse.statusText, - body: - clonedResponse.body === null ? null : await clonedResponse.text(), - headers: Object.fromEntries(clonedResponse.headers.entries()), - redirected: clonedResponse.redirected, - }, - }) - })() - } - - return response -} - -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -async function getResponse(event, client, requestId) { - const { request } = event - const clonedRequest = request.clone() - - function passthrough() { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) - - // Remove MSW-specific request headers so the bypassed requests - // comply with the server's CORS preflight check. - // Operate with the headers as an object because request "Headers" - // are immutable. - delete headers['x-msw-bypass'] - - return fetch(clonedRequest, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { - type: 'REQUEST', - payload: { - id: requestId, - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - mode: request.mode, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.text(), - bodyUsed: request.bodyUsed, - keepalive: request.keepalive, - }, - }) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'MOCK_NOT_FOUND': { - return passthrough() - } - - case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name - - // Rejecting a "respondWith" promise emulates a network error. - throw networkError - } - } - - return passthrough() -} - -function sendToClient(client, message) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [channel.port2]) - }) -} - -function sleep(timeMs) { - return new Promise((resolve) => { - setTimeout(resolve, timeMs) - }) -} - -async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) -} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt deleted file mode 100644 index 7b8e4fcf..00000000 --- a/frontend/public/robots.txt +++ /dev/null @@ -1,9 +0,0 @@ -User-agent: * -Disallow: /404 -Allow: / - -# Host -Host: https://funeat.site/ - -# Sitemaps -Sitemap: https://funeat.site/sitemap.xml \ No newline at end of file diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml deleted file mode 100644 index 9c261be7..00000000 --- a/frontend/public/sitemap.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - https://funeat.site - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/products/food - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/products/store - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/recipes - 2023-09-25T05:39:39+00:00 - - \ No newline at end of file diff --git a/frontend/src/apis/ApiClient.ts b/frontend/src/apis/ApiClient.ts deleted file mode 100644 index 26fe6284..00000000 --- a/frontend/src/apis/ApiClient.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { fetchApi } from './fetch'; - -interface RequestOptions { - params?: string; - queries?: string; - credentials?: boolean; -} - -export class ApiClient { - #path: string; - - #headers: HeadersInit; - - constructor(path: string, headers: HeadersInit = {}) { - this.#path = path; - this.#headers = headers; - } - - getUrl(params = '', queries = '') { - return '/api' + this.#path + params + queries; - } - - get({ params, queries, credentials = false }: RequestOptions) { - return fetchApi(this.getUrl(params, queries), { - method: 'GET', - headers: this.#headers, - credentials: credentials ? 'include' : 'omit', - }); - } - - post({ params, queries, credentials = false }: RequestOptions, headers?: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'POST', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - postData({ params, queries, credentials = false }: RequestOptions, body: FormData) { - return fetchApi(this.getUrl(params, queries), { - method: 'POST', - headers: this.#headers, - body: body, - credentials: credentials ? 'include' : 'omit', - }); - } - - patch({ params, queries, credentials = false }: RequestOptions, headers: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'PATCH', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - put({ params, queries, credentials = false }: RequestOptions, headers?: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'PUT', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - putData({ params, queries, credentials = false }: RequestOptions, body: FormData) { - return fetchApi(this.getUrl(params, queries), { - method: 'PUT', - headers: this.#headers, - body: body, - credentials: credentials ? 'include' : 'omit', - }); - } - - delete({ params, queries, credentials = false }: RequestOptions, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'DELETE', - headers: this.#headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } -} diff --git a/frontend/src/apis/fetch.ts b/frontend/src/apis/fetch.ts deleted file mode 100644 index 6c760b43..00000000 --- a/frontend/src/apis/fetch.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ErrorResponse } from '@/types/response'; - -export const fetchApi = async (url: string, options: RequestInit) => { - if (!navigator.onLine) { - throw new Error('네트워크 오프라인이 감지되었습니다'); - } - - const response = await fetch(url, options); - - if (!response.ok) { - const errorData: ErrorResponse = await response.json(); - throw new Error(errorData.message); - } - - return response; -}; diff --git a/frontend/src/apis/index.ts b/frontend/src/apis/index.ts deleted file mode 100644 index c258c161..00000000 --- a/frontend/src/apis/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ApiClient } from './ApiClient'; - -export const categoryApi = new ApiClient('/categories'); -export const productApi = new ApiClient('/products'); -export const tagApi = new ApiClient('/tags'); -export const rankApi = new ApiClient('/ranks'); -export const loginApi = new ApiClient('/login'); -export const memberApi = new ApiClient('/members'); -export const recipeApi = new ApiClient('/recipes'); -export const searchApi = new ApiClient('/search'); -export const logoutApi = new ApiClient('/logout'); -export const reviewApi = new ApiClient('/reviews'); -export const bannerApi = new ApiClient('/banners'); diff --git a/frontend/src/assets/characters.svg b/frontend/src/assets/characters.svg deleted file mode 100644 index 6580162c..00000000 --- a/frontend/src/assets/characters.svg +++ /dev/null @@ -1,63 +0,0 @@ - - -펀잇의 캐릭터 - -상품 미리보기 사진입니다. - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg deleted file mode 100644 index 26cb9858..00000000 --- a/frontend/src/assets/logo.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - 펀잇 로고 - -펀잇 로고 사진입니다. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/plate.svg b/frontend/src/assets/plate.svg deleted file mode 100644 index 1a5f2f61..00000000 --- a/frontend/src/assets/plate.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/samgakgimbab.svg b/frontend/src/assets/samgakgimbab.svg deleted file mode 100644 index a5e9872f..00000000 --- a/frontend/src/assets/samgakgimbab.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - 펀잇의 캐릭터 - -상품 미리보기 사진입니다. - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/components/Common/Banner/Banner.tsx b/frontend/src/components/Common/Banner/Banner.tsx deleted file mode 100644 index 89af08c6..00000000 --- a/frontend/src/components/Common/Banner/Banner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useBannerQuery } from '@/hooks/queries/banner'; - -const Banner = () => { - const { data: banners } = useBannerQuery(); - const { link, image } = banners[Math.floor(Math.random() * banners.length)]; - - if (!link) { - return ; - } - - return ( - - - - ); -}; - -export default Banner; - -const BannerImage = styled.img` - width: 100%; - height: auto; -`; diff --git a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx b/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx deleted file mode 100644 index 25de7b81..00000000 --- a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryFoodList from './CategoryFoodList'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryFoodList', - component: CategoryFoodList, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx b/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx deleted file mode 100644 index 921584b8..00000000 --- a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -import CategoryItem from '../CategoryItem/CategoryItem'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useCategoryFoodQuery } from '@/hooks/queries/product'; - -const categoryType = CATEGORY_TYPE.FOOD; - -const CategoryFoodList = () => { - const { data: categories } = useCategoryFoodQuery(categoryType); - - return ( - - {categories.map(({ id, name, image }) => ( - - ))} - - ); -}; - -export default CategoryFoodList; - -const CategoryFoodListWrapper = styled.div` - display: flex; - gap: 16px; -`; diff --git a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx b/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx deleted file mode 100644 index 7cbb6e1f..00000000 --- a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryFoodTab from './CategoryFoodTab'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryFoodTab', - component: CategoryFoodTab, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx b/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx deleted file mode 100644 index 56817526..00000000 --- a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Button, theme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context'; -import { useCategoryFoodQuery } from '@/hooks/queries/product/useCategoryQuery'; -import { getTargetCategoryName } from '@/utils/category'; - -const categoryType = CATEGORY_TYPE.FOOD; - -const CategoryFoodTab = () => { - const { data: categories } = useCategoryFoodQuery(categoryType); - - const { categoryIds } = useCategoryValueContext(); - const { selectCategory } = useCategoryActionContext(); - - const currentCategoryId = categoryIds[categoryType]; - - const { gaEvent } = useGA(); - - const handleCategoryButtonClick = (menuId: number) => { - selectCategory(categoryType, menuId); - gaEvent({ - category: 'button', - action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`, - label: '카테고리', - }); - }; - - return ( - - {categories.map(({ id, name }) => { - const isSelected = id === currentCategoryId; - return ( -
  • - handleCategoryButtonClick(id)} - aria-pressed={isSelected} - > - {name} - -
  • - ); - })} -
    - ); -}; - -export default CategoryFoodTab; - -const CategoryMenuContainer = styled.ul` - display: flex; - gap: 8px; - white-space: nowrap; - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } -`; - -const CategoryButton = styled(Button)<{ isSelected: boolean }>` - padding: 6px 12px; - ${({ isSelected }) => - isSelected && - ` - background: ${theme.colors.gray5}; - color: ${theme.textColors.white}; - `} -`; diff --git a/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx b/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx deleted file mode 100644 index 3dc17ddf..00000000 --- a/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryItem from './CategoryItem'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryItem', - component: CategoryItem, - decorators: [ - (Story) => ( - - - - ), - ], - args: { - categoryId: 1, - name: '즉석 식품', - image: 'https://tqklhszfkvzk6518638.cdn.ntruss.com/product/8801771029052.jpg', - categoryType: 'food', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryItem/CategoryItem.tsx b/frontend/src/components/Common/CategoryItem/CategoryItem.tsx deleted file mode 100644 index 051c9707..00000000 --- a/frontend/src/components/Common/CategoryItem/CategoryItem.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import { useNavigate } from 'react-router-dom'; -import styled from 'styled-components'; - -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext } from '@/hooks/context'; - -interface CategoryItemProps { - categoryId: number; - name: string; - image: string; - categoryType: 'food' | 'store'; -} - -const CategoryItem = ({ categoryId, name, image, categoryType }: CategoryItemProps) => { - const navigate = useNavigate(); - const { selectCategory } = useCategoryActionContext(); - - const { gaEvent } = useGA(); - - const handleCategoryItemClick = (categoryId: number) => { - selectCategory(categoryType, categoryId); - navigate(PATH.PRODUCT_LIST + '/' + categoryType); - - gaEvent({ - category: 'button', - action: `${name} 카테고리 링크 클릭`, - label: '카테고리', - }); - }; - - return ( - - ); -}; - -export default CategoryItem; - -const ImageWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - width: 60px; - height: 60px; - border-radius: 10px; - background: ${({ theme }) => theme.colors.white}; - - & > img { - width: 100%; - height: auto; - object-fit: cover; - } -`; - -const CategoryName = styled.p` - margin-top: 10px; - font-weight: 600; - font-size: ${({ theme }) => theme.fontSizes.xs}; -`; diff --git a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx b/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx deleted file mode 100644 index d26be6f4..00000000 --- a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryStoreList from './CategoryStoreList'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryStoreList', - component: CategoryStoreList, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx b/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx deleted file mode 100644 index 6bf2c36a..00000000 --- a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -import CategoryItem from '../CategoryItem/CategoryItem'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useCategoryStoreQuery } from '@/hooks/queries/product'; - -const categoryType = CATEGORY_TYPE.STORE; - -const CategoryStoreList = () => { - const { data: categories } = useCategoryStoreQuery(categoryType); - - return ( - - {categories.map(({ id, name, image }) => ( - - ))} - - ); -}; - -export default CategoryStoreList; - -const CategoryStoreListWrapper = styled.div` - display: flex; - gap: 16px; -`; diff --git a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx b/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx deleted file mode 100644 index 7fca5880..00000000 --- a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryStoreTab from './CategoryStoreTab'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryStoreTab', - component: CategoryStoreTab, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx b/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx deleted file mode 100644 index b75abb7b..00000000 --- a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Button, theme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context'; -import { useCategoryStoreQuery } from '@/hooks/queries/product/useCategoryQuery'; -import { getTargetCategoryName } from '@/utils/category'; - -const categoryType = CATEGORY_TYPE.STORE; - -const CategoryStoreTab = () => { - const { data: categories } = useCategoryStoreQuery(categoryType); - - const { categoryIds } = useCategoryValueContext(); - const { selectCategory } = useCategoryActionContext(); - const currentCategoryId = categoryIds[categoryType]; - - const { gaEvent } = useGA(); - - const handleCategoryButtonClick = (menuId: number) => { - selectCategory(categoryType, menuId); - gaEvent({ - category: 'button', - action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`, - label: '카테고리', - }); - }; - - return ( - - {categories.map(({ id, name }) => { - const isSelected = id === currentCategoryId; - return ( -
  • - handleCategoryButtonClick(id)} - aria-pressed={isSelected} - > - {name} - -
  • - ); - })} -
    - ); -}; - -export default CategoryStoreTab; - -const CategoryMenuContainer = styled.ul` - display: flex; - gap: 8px; - white-space: nowrap; - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } -`; - -const CategoryButton = styled(Button)<{ isSelected: boolean }>` - padding: 6px 12px; - ${({ isSelected }) => - isSelected && - ` - background: ${theme.colors.primary}; - color: ${theme.textColors.default}; - `} -`; diff --git a/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx b/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx deleted file mode 100644 index d8491896..00000000 --- a/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { ComponentType, PropsWithChildren } from 'react'; -import { Component } from 'react'; - -export interface FallbackProps { - message: string; -} - -interface ErrorBoundaryProps { - handleReset?: () => void; - fallback: ComponentType; -} - -interface ErrorBoundaryState { - error: Error | null; -} - -class ErrorBoundary extends Component, ErrorBoundaryState> { - state: ErrorBoundaryState = { - error: null, - }; - - static getDerivedStateFromError(error: Error): ErrorBoundaryState { - return { error }; - } - - resetError = () => { - if (this.props.handleReset) { - this.props.handleReset(); - } - - this.setState({ error: null }); - }; - - render() { - const { fallback: FallbackComponent } = this.props; - - if (this.state.error) { - return ( - <> - - - - ); - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx b/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx deleted file mode 100644 index ee51fcea..00000000 --- a/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ErrorComponent from './ErrorComponent'; - -const meta: Meta = { - title: 'common/ErrorComponent', - component: ErrorComponent, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx b/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx deleted file mode 100644 index bc66e1f1..00000000 --- a/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const ErrorComponent = () => { - return
    에러가 발생했습니다.
    ; -}; - -export default ErrorComponent; diff --git a/frontend/src/components/Common/Header/Header.stories.tsx b/frontend/src/components/Common/Header/Header.stories.tsx deleted file mode 100644 index b88b4258..00000000 --- a/frontend/src/components/Common/Header/Header.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Header from './Header'; - -const meta: Meta = { - title: 'common/Header', - component: Header, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/Header/Header.tsx b/frontend/src/components/Common/Header/Header.tsx deleted file mode 100644 index ae2431ed..00000000 --- a/frontend/src/components/Common/Header/Header.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import Logo from '@/assets/logo.svg'; -import { PATH } from '@/constants/path'; - -interface HeaderProps { - hasSearch?: boolean; -} - -const Header = ({ hasSearch = true }: HeaderProps) => { - if (hasSearch) { - return ( - - - - - - - - - ); - } - - return ( - - - - - - ); -}; - -export default Header; - -const HeaderWithSearchContainer = styled.header` - display: flex; - justify-content: space-between; - align-items: center; - width: calc(100% - 40px); - height: 60px; - margin: 0 auto; -`; - -const HeaderContainer = styled.header` - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 60px; -`; diff --git a/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx b/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx deleted file mode 100644 index 02de4489..00000000 --- a/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ImageUploader from './ImageUploader'; - -const meta: Meta = { - title: 'common/ImageUploader', - component: ImageUploader, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx b/frontend/src/components/Common/ImageUploader/ImageUploader.tsx deleted file mode 100644 index b139a1b4..00000000 --- a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Button, useToastActionContext } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -import { IMAGE_MAX_SIZE } from '@/constants'; -import { useEnterKeyDown } from '@/hooks/common'; - -interface ReviewImageUploaderProps { - previewImage: string; - uploadImage: (imageFile: File) => void; - deleteImage: () => void; -} - -const ImageUploader = ({ previewImage, uploadImage, deleteImage }: ReviewImageUploaderProps) => { - const { inputRef, handleKeydown } = useEnterKeyDown(); - const { toast } = useToastActionContext(); - - const handleImageUpload: ChangeEventHandler = (event) => { - if (!event.target.files) { - return; - } - - const imageFile = event.target.files[0]; - - if (imageFile.size > IMAGE_MAX_SIZE) { - toast.error('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.'); - event.target.value = ''; - return; - } - - uploadImage(imageFile); - }; - - return ( - <> - {previewImage ? ( - - 업로드한 사진 - - - ) : ( - - + - - - )} - - ); -}; - -export default ImageUploader; - -const ImageUploadLabel = styled.label` - display: flex; - justify-content: center; - align-items: center; - width: 92px; - height: 95px; - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - border-radius: ${({ theme }) => theme.borderRadius.xs}; - background: ${({ theme }) => theme.colors.gray1}; - cursor: pointer; - - & > input { - display: none; - } -`; - -const PreviewImageWrapper = styled.div` - display: flex; - flex-direction: column; - gap: 20px; - align-items: center; -`; diff --git a/frontend/src/components/Common/Loading/Loading.stories.tsx b/frontend/src/components/Common/Loading/Loading.stories.tsx deleted file mode 100644 index 3b866175..00000000 --- a/frontend/src/components/Common/Loading/Loading.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Loading from './Loading'; - -const meta: Meta = { - title: 'common/Loading', - component: Loading, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/Loading/Loading.tsx b/frontend/src/components/Common/Loading/Loading.tsx deleted file mode 100644 index 7c58614a..00000000 --- a/frontend/src/components/Common/Loading/Loading.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Text } from '@fun-eat/design-system'; -import styled, { keyframes } from 'styled-components'; - -import PlateImage from '@/assets/plate.svg'; - -const DEFAULT_DESCRIPTION = '잠시만 기다려주세요 🥄'; - -interface LoadingProps { - customHeight?: string; - description?: string; -} - -const Loading = ({ customHeight = '100%', description = DEFAULT_DESCRIPTION }: LoadingProps) => { - return ( - - - - - {description} - - ); -}; - -export default Loading; - -type LoadingContainerStyleProps = Pick; - -const LoadingContainer = styled.div` - display: flex; - flex-direction: column; - row-gap: 36px; - justify-content: center; - align-items: center; - height: ${({ customHeight }) => customHeight}; -`; - -const rotate = keyframes` - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -`; - -const PlateImageWrapper = styled.div` - animation: ${rotate} 1.5s ease-in-out infinite; -`; diff --git a/frontend/src/components/Common/MarkedText/MarkedText.tsx b/frontend/src/components/Common/MarkedText/MarkedText.tsx deleted file mode 100644 index 341b3c92..00000000 --- a/frontend/src/components/Common/MarkedText/MarkedText.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Fragment } from 'react'; -import styled from 'styled-components'; - -interface MarkedTextProps { - text: string; - mark: string; -} - -const MarkedText = ({ text, mark }: MarkedTextProps) => { - const textFragments = text.split(new RegExp(`(${mark})`, 'gi')); - - return ( - <> - {textFragments.map((fragment, index) => ( - - {fragment.toLowerCase() === mark.toLowerCase() ? {fragment} : <>{fragment}} - - ))} - - ); -}; - -export default MarkedText; - -const Mark = styled.mark` - font-weight: ${({ theme }) => theme.fontWeights.bold}; - background-color: ${({ theme }) => theme.backgroundColors.default}; -`; diff --git a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx b/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx deleted file mode 100644 index 739bde3e..00000000 --- a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import NavigableSectionTitle from './NavigableSectionTitle'; - -const meta: Meta = { - title: 'common/NavigableSectionTitle', - component: NavigableSectionTitle, - args: { - title: '내가 작성한 리뷰 (12개)', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx b/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx deleted file mode 100644 index a24ba0ed..00000000 --- a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; - -interface NavigableSectionTitleProps { - title: string; - routeDestination: string; -} - -const NavigableSectionTitle = ({ title, routeDestination }: NavigableSectionTitleProps) => { - return ( - - - {title} - - - - - - ); -}; - -export default NavigableSectionTitle; - -const NavigableSectionTitleContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const ArrowIcon = styled(SvgIcon)` - transform: translateY(3px) rotate(180deg); -`; diff --git a/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx b/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx deleted file mode 100644 index a9242715..00000000 --- a/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import NavigationBar from './NavigationBar'; - -const meta: Meta = { - title: 'common/NavigationBar', - component: NavigationBar, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Common/NavigationBar/NavigationBar.tsx b/frontend/src/components/Common/NavigationBar/NavigationBar.tsx deleted file mode 100644 index 8cbfbee3..00000000 --- a/frontend/src/components/Common/NavigationBar/NavigationBar.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Link, Text, theme } from '@fun-eat/design-system'; -import { Link as RouterLink, useLocation } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import { NAVIGATION_MENU } from '@/constants'; - -const NavigationBar = () => { - const location = useLocation(); - - return ( - - - {NAVIGATION_MENU.map(({ variant, name, path }) => { - const currentPath = location.pathname.split('/')[1]; - const isSelected = currentPath === path.split('/')[1]; - - return ( - - - - - {name} - - - - ); - })} - - - ); -}; - -export default NavigationBar; - -const NavigationBarContainer = styled.nav` - width: 100%; - height: 60px; -`; - -const NavigationBarList = styled.ul` - display: flex; - justify-content: space-around; - align-items: center; - padding-top: 12px; - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - border-bottom: none; - border-top-left-radius: 20px; - border-top-right-radius: 20px; -`; - -const NavigationItem = styled.li` - height: 50px; -`; - -const NavigationLink = styled(Link)` - display: flex; - flex-direction: column; - gap: 4px; - justify-content: flex-end; - align-items: center; -`; diff --git a/frontend/src/components/Common/RegisterButton/RegisterButton.tsx b/frontend/src/components/Common/RegisterButton/RegisterButton.tsx deleted file mode 100644 index 1601e560..00000000 --- a/frontend/src/components/Common/RegisterButton/RegisterButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useMemberQuery } from '@/hooks/queries/members'; - -interface RegisterButtonProps { - activeLabel: string; - disabledLabel: string; - onClick: () => void; -} - -const RegisterButton = ({ activeLabel, disabledLabel, onClick }: RegisterButtonProps) => { - const { data: member } = useMemberQuery(); - - return ( - - {member ? activeLabel : disabledLabel} - - ); -}; - -export default RegisterButton; - -const RegisterButtonContainer = styled(Button)` - cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; -`; diff --git a/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx b/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx deleted file mode 100644 index 50b796d2..00000000 --- a/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ScrollButton from './ScrollButton'; - -const meta: Meta = { - title: 'common/ScrollButton', - component: ScrollButton, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ScrollButton/ScrollButton.tsx b/frontend/src/components/Common/ScrollButton/ScrollButton.tsx deleted file mode 100644 index 6bfcc509..00000000 --- a/frontend/src/components/Common/ScrollButton/ScrollButton.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { RefObject } from 'react'; -import { styled } from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import { useScroll } from '@/hooks/common'; - -interface ScrollButtonProps { - targetRef: RefObject; - isRecipePage?: boolean; -} - -const ScrollButton = ({ targetRef, isRecipePage = false }: ScrollButtonProps) => { - const { scrollToTop } = useScroll(); - - const handleScroll = () => { - if (targetRef) { - scrollToTop(targetRef); - } - }; - - return ( - - - - ); -}; - -export default ScrollButton; - -const ScrollButtonWrapper = styled(Button)>` - position: fixed; - bottom: ${({ isRecipePage }) => (isRecipePage ? '210px' : '90px')}; - right: 20px; - border-radius: 50%; - box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 4px; - - @media screen and (min-width: 600px) { - left: calc(50% + 234px); - } - - &:hover { - transform: scale(1.1); - transition: all 200ms ease-in-out; - } - - svg { - rotate: 90deg; - } -`; diff --git a/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx b/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx deleted file mode 100644 index 05883aea..00000000 --- a/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import SectionTitle from './SectionTitle'; - -const meta: Meta = { - title: 'common/SectionTitle', - component: SectionTitle, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - name: '사이다', - }, -}; - -export const Bookmarked: Story = { - args: { - name: '사이다', - }, -}; diff --git a/frontend/src/components/Common/SectionTitle/SectionTitle.tsx b/frontend/src/components/Common/SectionTitle/SectionTitle.tsx deleted file mode 100644 index c9d649dd..00000000 --- a/frontend/src/components/Common/SectionTitle/SectionTitle.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Button, Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useRoutePage } from '@/hooks/common'; - -interface SectionTitleProps { - name: string; - link?: string; -} - -const SectionTitle = ({ name, link }: SectionTitleProps) => { - const { routeBack } = useRoutePage(); - - return ( - - - - {link ? ( - - {name} - - ) : ( - {name} - )} - {link && } - - - ); -}; - -export default SectionTitle; - -const SectionTitleContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const SectionTitleWrapper = styled.div` - display: flex; - align-items: center; - - svg { - padding-top: 2px; - } -`; - -const ProductName = styled(Heading)` - margin: 0 5px 0 16px; -`; diff --git a/frontend/src/components/Common/SortButton/SortButton.stories.tsx b/frontend/src/components/Common/SortButton/SortButton.stories.tsx deleted file mode 100644 index 004845ef..00000000 --- a/frontend/src/components/Common/SortButton/SortButton.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import SortButton from './SortButton'; - -import { PRODUCT_SORT_OPTIONS } from '@/constants'; - -const meta: Meta = { - title: 'common/SortButton', - component: SortButton, - args: { - option: PRODUCT_SORT_OPTIONS[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/SortButton/SortButton.tsx b/frontend/src/components/Common/SortButton/SortButton.tsx deleted file mode 100644 index 54d7acc1..00000000 --- a/frontend/src/components/Common/SortButton/SortButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import type { SortOption } from '@/types/common'; - -interface SortButtonProps { - option: SortOption; - onClick: () => void; -} - -const SortButton = ({ option, onClick }: SortButtonProps) => { - const theme = useTheme(); - - return ( - - - {option.label} - - ); -}; - -export default SortButton; - -const SortButtonContainer = styled(Button)` - display: flex; - justify-content: flex-end; - align-items: center; - padding: 0; - column-gap: 4px; -`; diff --git a/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx b/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx deleted file mode 100644 index 5e7c2f93..00000000 --- a/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { BottomSheet, useBottomSheet } from '@fun-eat/design-system'; -import type { Meta, StoryObj } from '@storybook/react'; -import { useEffect } from 'react'; - -import SortOptionList from './SortOptionList'; - -import { PRODUCT_SORT_OPTIONS } from '@/constants'; -import { useSortOption } from '@/hooks/common'; - -const meta: Meta = { - title: 'common/SortOptionList', - component: SortOptionList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => { - const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet(); - const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]); - - useEffect(() => { - handleOpenBottomSheet(); - }, []); - - return ( - - - - ); - }, -}; diff --git a/frontend/src/components/Common/SortOptionList/SortOptionList.tsx b/frontend/src/components/Common/SortOptionList/SortOptionList.tsx deleted file mode 100644 index fb62c72c..00000000 --- a/frontend/src/components/Common/SortOptionList/SortOptionList.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { SortOption } from '@/types/common'; - -interface SortOptionListProps { - options: readonly SortOption[]; - selectedOption: SortOption; - selectSortOption: (selectedOptionLabel: SortOption) => void; - close: () => void; -} - -const SortOptionList = ({ options, selectedOption, selectSortOption, close }: SortOptionListProps) => { - const handleSelectedOption = (sortOption: SortOption) => { - selectSortOption(sortOption); - close(); - }; - - return ( - - {options.map((sortOption) => { - const isSelected = sortOption.label === selectedOption.label; - return ( -
  • - handleSelectedOption(sortOption)} - > - {sortOption.label} - -
  • - ); - })} -
    - ); -}; - -export default SortOptionList; - -const SortOptionListContainer = styled.ul` - padding: 20px; - - & > li { - height: 60px; - line-height: 60px; - border-bottom: 1px solid ${({ theme }) => theme.dividerColors.disabled}; - } - - & > li:last-of-type { - border: none; - } -`; - -const SortOptionButton = styled(Button)` - padding: 10px 0; - text-align: left; - border: none; - outline: transparent; - - &:hover { - color: ${({ theme }) => theme.textColors.default}; - font-weight: ${({ theme }) => theme.fontWeights.bold}; - transition: all 200ms ease-in; - } -`; diff --git a/frontend/src/components/Common/Svg/SvgIcon.stories.tsx b/frontend/src/components/Common/Svg/SvgIcon.stories.tsx deleted file mode 100644 index 87c7f7e0..00000000 --- a/frontend/src/components/Common/Svg/SvgIcon.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { theme } from '@fun-eat/design-system'; -import type { Meta, StoryObj } from '@storybook/react'; - -import SvgIcon, { SVG_ICON_VARIANTS } from './SvgIcon'; - -const meta: Meta = { - title: 'common/SvgIcon', - component: SvgIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, - args: { - variant: 'recipe', - color: theme.colors.gray4, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = {}; - -export const SvgIcons: Story = { - render: () => { - return ( - <> - {SVG_ICON_VARIANTS.map((variant) => ( - - ))} - - ); - }, -}; diff --git a/frontend/src/components/Common/Svg/SvgIcon.tsx b/frontend/src/components/Common/Svg/SvgIcon.tsx deleted file mode 100644 index e31256ae..00000000 --- a/frontend/src/components/Common/Svg/SvgIcon.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { theme } from '@fun-eat/design-system'; -import type { ComponentPropsWithoutRef, CSSProperties } from 'react'; - -export const SVG_ICON_VARIANTS = [ - 'recipe', - 'list', - 'member', - 'search', - 'arrow', - 'bookmark', - 'bookmarkFilled', - 'review', - 'star', - 'favorite', - 'favoriteFilled', - 'home', - 'sort', - 'kakao', - 'close', - 'triangle', - 'plus', - 'pencil', - 'camera', - 'link', - 'plane', - 'info', - 'trashcan', -] as const; -export type SvgIconVariant = (typeof SVG_ICON_VARIANTS)[number]; - -interface SvgIconProps extends ComponentPropsWithoutRef<'svg'> { - /** - * SvgSprite 컴포넌트의 symbol id입니다. - */ - variant: SvgIconVariant; - /** - * SvgIcon의 색상입니다. (기본값 gray4) - */ - color?: CSSProperties['color']; - /** - * SvgIcon의 너비입니다. (기본값 24) - */ - width?: number; - /** - * SvgIcon의 높이입니다. (기본값 24) - */ - height?: number; -} - -const SvgIcon = ({ variant, width = 24, height = 24, color = theme.colors.gray4, ...props }: SvgIconProps) => { - return ( - - - - ); -}; - -export default SvgIcon; diff --git a/frontend/src/components/Common/Svg/SvgSprite.tsx b/frontend/src/components/Common/Svg/SvgSprite.tsx deleted file mode 100644 index e0fa06dd..00000000 --- a/frontend/src/components/Common/Svg/SvgSprite.tsx +++ /dev/null @@ -1,96 +0,0 @@ -const SvgSprite = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SvgSprite; diff --git a/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx b/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx deleted file mode 100644 index 4ba72445..00000000 --- a/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import TabMenu from './TabMenu'; - -const meta: Meta = { - title: 'common/TabMenu', - component: TabMenu, - args: { - tabMenus: ['리뷰 1,200', '꿀조합'], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: ({ ...args }) => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Common/TabMenu/TabMenu.tsx b/frontend/src/components/Common/TabMenu/TabMenu.tsx deleted file mode 100644 index 548b2f36..00000000 --- a/frontend/src/components/Common/TabMenu/TabMenu.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { ForwardedRef, MouseEventHandler } from 'react'; -import { forwardRef } from 'react'; -import styled from 'styled-components'; - -interface TabMenuProps { - tabMenus: readonly string[]; - selectedTabMenu: number; - handleTabMenuSelect: (index: number) => void; -} - -const TabMenu = ( - { tabMenus, selectedTabMenu, handleTabMenuSelect }: TabMenuProps, - ref: ForwardedRef -) => { - const handleTabMenuClick: MouseEventHandler = (event) => { - const { index } = event.currentTarget.dataset; - - if (index) { - handleTabMenuSelect(Number(index)); - } - }; - - return ( - - {tabMenus.map((menu, index) => { - const isSelected = selectedTabMenu === index; - return ( - - - {menu} - - - ); - })} - - ); -}; - -export default forwardRef(TabMenu); - -const TabMenuContainer = styled.ul` - display: flex; -`; - -const TabMenuItem = styled.li<{ isSelected: boolean }>` - flex-grow: 1; - width: 50%; - height: 45px; - border-bottom: 2px solid - ${({ isSelected, theme }) => (isSelected ? theme.borderColors.strong : theme.borderColors.disabled)}; -`; - -const TabMenuButton = styled(Button)` - padding: 0; - line-height: 45px; -`; diff --git a/frontend/src/components/Common/TagList/TagList.stories.tsx b/frontend/src/components/Common/TagList/TagList.stories.tsx deleted file mode 100644 index 4ac76785..00000000 --- a/frontend/src/components/Common/TagList/TagList.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import TagList from './TagList'; - -import productDetails from '@/mocks/data/productDetails.json'; - -const meta: Meta = { - title: 'common/TagList', - component: TagList, - args: { - tags: productDetails[0].tags, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/TagList/TagList.tsx b/frontend/src/components/Common/TagList/TagList.tsx deleted file mode 100644 index b36cd70a..00000000 --- a/frontend/src/components/Common/TagList/TagList.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Badge } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { Tag } from '@/types/common'; -import { convertTagColor } from '@/utils/convertTagColor'; - -interface TagListProps { - tags: Tag[]; -} - -const TagList = ({ tags }: TagListProps) => { - return ( - - {tags.map((tag) => { - const tagColor = convertTagColor(tag.tagType); - return ( -
  • - - {tag.name} - -
  • - ); - })} -
    - ); -}; - -export default TagList; - -const TagListContainer = styled.ul` - display: flex; - margin: 12px 0; - column-gap: 8px; -`; - -const TagBadge = styled(Badge)` - font-weight: bold; -`; diff --git a/frontend/src/components/Common/index.ts b/frontend/src/components/Common/index.ts deleted file mode 100644 index f5b21821..00000000 --- a/frontend/src/components/Common/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { default as CategoryFoodTab } from './CategoryFoodTab/CategoryFoodTab'; -export { default as CategoryStoreTab } from './CategoryStoreTab/CategoryStoreTab'; -export { default as Header } from './Header/Header'; -export { default as NavigationBar } from './NavigationBar/NavigationBar'; -export { default as SortButton } from './SortButton/SortButton'; -export { default as SortOptionList } from './SortOptionList/SortOptionList'; -export { default as SvgSprite } from './Svg/SvgSprite'; -export { default as SvgIcon } from './Svg/SvgIcon'; -export { default as TabMenu } from './TabMenu/TabMenu'; -export { default as TagList } from './TagList/TagList'; -export { default as SectionTitle } from './SectionTitle/SectionTitle'; -export { default as ScrollButton } from './ScrollButton/ScrollButton'; -export { default as ImageUploader } from './ImageUploader/ImageUploader'; -export { default as ErrorBoundary } from './ErrorBoundary/ErrorBoundary'; -export { default as ErrorComponent } from './ErrorComponent/ErrorComponent'; -export { default as Loading } from './Loading/Loading'; -export { default as MarkedText } from './MarkedText/MarkedText'; -export { default as NavigableSectionTitle } from './NavigableSectionTitle/NavigableSectionTitle'; -export { default as RegisterButton } from './RegisterButton/RegisterButton'; -export { default as CategoryItem } from './CategoryItem/CategoryItem'; -export { default as CategoryFoodList } from './CategoryFoodList/CategoryFoodList'; -export { default as CategoryStoreList } from './CategoryStoreList/CategoryStoreList'; -export { default as Banner } from './Banner/Banner'; diff --git a/frontend/src/components/Layout/AuthLayout.tsx b/frontend/src/components/Layout/AuthLayout.tsx deleted file mode 100644 index 0cc0570c..00000000 --- a/frontend/src/components/Layout/AuthLayout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Navigate } from 'react-router-dom'; - -import { PATH } from '@/constants/path'; -import { useMemberQuery } from '@/hooks/queries/members'; - -interface AuthLayoutProps { - children: JSX.Element; -} - -const AuthLayout = ({ children }: AuthLayoutProps) => { - const { data: member } = useMemberQuery(); - - if (!member) { - return ; - } - - return children; -}; - -export default AuthLayout; diff --git a/frontend/src/components/Layout/DefaultLayout.tsx b/frontend/src/components/Layout/DefaultLayout.tsx deleted file mode 100644 index 39f1f8cf..00000000 --- a/frontend/src/components/Layout/DefaultLayout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; -import NavigationBar from '../Common/NavigationBar/NavigationBar'; - -const DefaultLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - - ); -}; - -export default DefaultLayout; - -const DefaultLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 120px); - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/HeaderOnlyLayout.tsx b/frontend/src/components/Layout/HeaderOnlyLayout.tsx deleted file mode 100644 index ec3ad899..00000000 --- a/frontend/src/components/Layout/HeaderOnlyLayout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; - -const HeaderOnlyLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - ); -}; - -export default HeaderOnlyLayout; - -const HeaderOnlyLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 60px); - padding: 20px; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/MinimalLayout.tsx b/frontend/src/components/Layout/MinimalLayout.tsx deleted file mode 100644 index 1fdfc36f..00000000 --- a/frontend/src/components/Layout/MinimalLayout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -const MinimalLayout = ({ children }: PropsWithChildren) => { - return ( - - {children} - - ); -}; - -export default MinimalLayout; - -const MinimalLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: 100%; - padding: 20px; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/SimpleHeaderLayout.tsx b/frontend/src/components/Layout/SimpleHeaderLayout.tsx deleted file mode 100644 index 4f17b93a..00000000 --- a/frontend/src/components/Layout/SimpleHeaderLayout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; -import NavigationBar from '../Common/NavigationBar/NavigationBar'; - -const SimpleHeaderLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - - ); -}; - -export default SimpleHeaderLayout; - -const SimpleHeaderLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 120px); - padding: 20px 20px 0; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/index.ts b/frontend/src/components/Layout/index.ts deleted file mode 100644 index 69ec1a40..00000000 --- a/frontend/src/components/Layout/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as DefaultLayout } from './DefaultLayout'; -export { default as MinimalLayout } from './MinimalLayout'; -export { default as HeaderOnlyLayout } from './HeaderOnlyLayout'; -export { default as AuthLayout } from './AuthLayout'; -export { default as SimpleHeaderLayout } from './SimpleHeaderLayout'; diff --git a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx b/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx deleted file mode 100644 index 6a03e450..00000000 --- a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Heading, Spacing, Text, Input, useTheme } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -const MIN_LENGTH = 1; -const MAX_LENGTH = 10; - -interface MemberModifyInputProps { - nickname: string; - modifyNickname: ChangeEventHandler; -} - -const MemberModifyInput = ({ nickname, modifyNickname }: MemberModifyInputProps) => { - const theme = useTheme(); - - return ( - - - 닉네임 - - - {nickname.length}자 / {MAX_LENGTH}자 - - - - - ); -}; - -export default MemberModifyInput; - -const MemberModifyInputContainer = styled.div` - position: relative; -`; - -const NicknameStatusText = styled(Text)` - position: absolute; - top: 0; - right: 0; -`; diff --git a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx b/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx deleted file mode 100644 index 9b28324e..00000000 --- a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberRecipeList from './MemberRecipeList'; - -const meta: Meta = { - title: 'members/ MemberRecipeList', - component: MemberRecipeList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx b/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx deleted file mode 100644 index 740daddf..00000000 --- a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Link, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { RecipeItem } from '@/components/Recipe'; -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteMemberRecipeQuery } from '@/hooks/queries/members'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface MemberRecipeListProps { - isPreview?: boolean; -} - -const MemberRecipeList = ({ isPreview = false }: MemberRecipeListProps) => { - const scrollRef = useRef(null); - - const { fetchNextPage, hasNextPage, data } = useInfiniteMemberRecipeQuery(); - const memberRecipes = data?.pages.flatMap((page) => page.recipes); - const recipeToDisplay = useDisplaySlice(isPreview, memberRecipes); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const totalRecipeCount = data?.pages[0].page.totalDataCount; - - if (totalRecipeCount === 0) { - return ( - - - 앗, 작성한 꿀조합이 없네요 🥲 - - - - 꿀조합 작성하러 가기 - - - ); - } - - return ( - - {!isPreview && ( - - 총 {totalRecipeCount}개의 꿀조합을 남겼어요! - - )} - - - {recipeToDisplay?.map((recipe) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default MemberRecipeList; - -const MemberRecipeListContainer = styled.section` - display: flex; - flex-direction: column; -`; - -const MemberRecipeListWrapper = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; - -const TotalRecipeCount = styled(Text)` - text-align: right; -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin-top: 20px; -`; - -const RecipeLink = styled(Link)` - padding: 12px 12px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx deleted file mode 100644 index 3856648f..00000000 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberReviewItem from './MemberReviewItem'; - -const meta: Meta = { - title: 'members/MemberReviewItem', - component: MemberReviewItem, - args: { - review: { - reviewId: 1, - productId: 5, - productName: '구운감자슬림명란마요', - content: - '할머니가 먹을 거 같은 맛입니다. 1960년 전쟁 때 맛 보고 싶었는데 그때는 너무 가난해서 먹을 수 없었는데요 이것보다 긴 리뷰도 잘려 보인답니다', - rating: 4.0, - favoriteCount: 1256, - categoryType: 'food', - }, - isPreview: true, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx deleted file mode 100644 index 3024b982..00000000 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useTheme, Spacing, Text, Button, useToastActionContext } from '@fun-eat/design-system'; -import type { MouseEventHandler } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useDeleteReview } from '@/hooks/queries/members'; -import type { MemberReview } from '@/types/review'; - -interface MemberReviewItemProps { - review: MemberReview; - isPreview: boolean; -} - -const MemberReviewItem = ({ review, isPreview }: MemberReviewItemProps) => { - const theme = useTheme(); - - const { mutate } = useDeleteReview(); - - const { toast } = useToastActionContext(); - - const { reviewId, productName, content, rating, favoriteCount } = review; - - const handleReviewDelete: MouseEventHandler = (e) => { - e.preventDefault(); - - const result = window.confirm('리뷰를 삭제하시겠습니까?'); - if (!result) { - return; - } - - mutate(reviewId, { - onSuccess: () => { - toast.success('리뷰를 삭제했습니다.'); - }, - onError: (error) => { - if (error instanceof Error) { - toast.error(error.message); - return; - } - - toast.error('리뷰 좋아요를 다시 시도해주세요.'); - }, - }); - }; - - return ( - - - - {productName} - - {!isPreview && ( - - )} - - - {content} - - - - - - - {favoriteCount} - - - - - - {rating.toFixed(1)} - - - - - ); -}; - -export default MemberReviewItem; - -const ReviewRankingItemContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px 0; - border-bottom: ${({ theme }) => `1px solid ${theme.borderColors.disabled}`}; -`; - -const ProductNameIconWrapper = styled.div` - display: flex; - justify-content: space-between; -`; - -const ReviewText = styled(Text)` - display: -webkit-inline-box; - text-overflow: ellipsis; - overflow: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -`; - -const FavoriteStarWrapper = styled.div` - display: flex; - gap: 4px; -`; - -const FavoriteIconWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const RatingIconWrapper = styled.div` - display: flex; - gap: 2px; - align-items: center; - - & > svg { - padding-bottom: 2px; - } -`; diff --git a/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx b/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx deleted file mode 100644 index 97c95ac9..00000000 --- a/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberReviewList from './MemberReviewList'; - -const meta: Meta = { - title: 'members/MemberReviewList', - component: MemberReviewList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx b/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx deleted file mode 100644 index b622d9f6..00000000 --- a/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Link, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import MemberReviewItem from '../MemberReviewItem/MemberReviewItem'; - -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteMemberReviewQuery } from '@/hooks/queries/members'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface MemberReviewListProps { - isPreview?: boolean; -} - -const MemberReviewList = ({ isPreview = false }: MemberReviewListProps) => { - const scrollRef = useRef(null); - const { fetchNextPage, hasNextPage, data } = useInfiniteMemberReviewQuery(); - const memberReviews = data.pages.flatMap((page) => page.reviews); - const reviewsToDisplay = useDisplaySlice(isPreview, memberReviews); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const totalReviewCount = data.pages[0].page.totalDataCount; - - if (totalReviewCount === 0) { - return ( - - - 앗, 작성한 리뷰가 없네요 🥲 - - - - 리뷰 작성하러 가기 - - - ); - } - - return ( - - {!isPreview && ( - - 총 {totalReviewCount}개의 리뷰를 남겼어요! - - )} - - - {reviewsToDisplay.map((review) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default MemberReviewList; - -const MemberReviewListContainer = styled.section` - display: flex; - flex-direction: column; -`; - -const MemberReviewListWrapper = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; - -const TotalReviewCount = styled(Text)` - text-align: right; -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin-top: 20px; -`; - -const ReviewLink = styled(Link)` - padding: 12px 12px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Members/MembersInfo/MembersInfo.tsx b/frontend/src/components/Members/MembersInfo/MembersInfo.tsx deleted file mode 100644 index 1b2e9483..00000000 --- a/frontend/src/components/Members/MembersInfo/MembersInfo.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button, Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { PATH } from '@/constants/path'; -import { useLogoutMutation, useMemberQuery } from '@/hooks/queries/members'; - -const MembersInfo = () => { - const { data: member } = useMemberQuery(); - const { mutate } = useLogoutMutation(); - - if (!member) { - return null; - } - - const { nickname, profileImage } = member; - - const handleLogout = () => { - mutate(); - }; - - return ( - - - - - {nickname} 님 - - - - - - - - ); -}; - -export default MembersInfo; - -const MembersInfoContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const MemberInfoWrapper = styled.div` - display: flex; - align-items: center; -`; - -const MemberModifyLink = styled(Link)` - margin-left: 5px; - transform: translateY(1px); -`; - -const MembersImage = styled.img` - margin-right: 16px; - border: 2px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; - object-fit: cover; -`; diff --git a/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx b/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx deleted file mode 100644 index a44e59f4..00000000 --- a/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MembersInfo from './MembersInfo'; - -import mockMember from '@/mocks/data/members.json'; - -const meta: Meta = { - title: 'members/MembersInfo', - component: MembersInfo, - args: { - member: mockMember, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/index.ts b/frontend/src/components/Members/index.ts deleted file mode 100644 index 4e31460e..00000000 --- a/frontend/src/components/Members/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as MembersInfo } from './MembersInfo/MembersInfo'; -export { default as MemberReviewList } from './MemberReviewList/MemberReviewList'; -export { default as MemberRecipeList } from './MemberRecipeList/MemberRecipeList'; -export { default as MemberModifyInput } from './MemberModifyInput/MemberModifyInput'; -export { default as MemberReviewItem } from './MemberReviewItem/MemberReviewItem'; diff --git a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx b/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx deleted file mode 100644 index 32226fec..00000000 --- a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductDetailItem from './ProductDetailItem'; - -import productDetail from '@/mocks/data/productDetail.json'; - -const meta: Meta = { - title: 'product/ProductDetailItem', - component: ProductDetailItem, - args: { - productDetail: productDetail, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: ({ ...args }) => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx b/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx deleted file mode 100644 index b91b97e2..00000000 --- a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; -import PBPreviewImage from '@/assets/samgakgimbab.svg'; -import { SvgIcon, TagList } from '@/components/Common'; -import { CATEGORY_TYPE } from '@/constants'; -import type { ProductDetail } from '@/types/product'; - -interface ProductDetailItemProps { - category: string; - productDetail: ProductDetail; -} - -const ProductDetailItem = ({ category, productDetail }: ProductDetailItemProps) => { - const { name, price, image, content, averageRating, tags } = productDetail; - - const theme = useTheme(); - - return ( - - - {image ? ( - {name} - ) : category === CATEGORY_TYPE.FOOD ? ( - - ) : ( - - )} - - - - - 가격 - {price.toLocaleString('ko-KR')}원 - - - 상품 설명 - {content} - - - 평균 평점 - - - {averageRating.toFixed(1)} - - - - - ); -}; - -export default ProductDetailItem; - -const ProductDetailContainer = styled.div` - display: flex; - flex-direction: column; - row-gap: 30px; - g & > img, - svg { - align-self: center; - } -`; - -const ImageWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; -`; - -const DetailInfoWrapper = styled.div` - & > div + div { - margin-top: 10px; - } -`; - -const DescriptionWrapper = styled.div` - display: flex; - column-gap: 20px; - - & > p:first-of-type { - flex-shrink: 0; - width: 60px; - } -`; - -const ProductContent = styled(Text)` - white-space: pre-wrap; -`; - -const RatingIconWrapper = styled.div` - display: flex; - align-items: center; - margin-left: -4px; - column-gap: 4px; - - & > svg { - padding-bottom: 2px; - } -`; diff --git a/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx b/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx deleted file mode 100644 index 6afb4a23..00000000 --- a/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductItem from './ProductItem'; - -import mockProducts from '@/mocks/data/products.json'; - -const meta: Meta = { - title: 'product/ProductItem', - component: ProductItem, - args: { - product: mockProducts.products[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductItem/ProductItem.tsx b/frontend/src/components/Product/ProductItem/ProductItem.tsx deleted file mode 100644 index d961894d..00000000 --- a/frontend/src/components/Product/ProductItem/ProductItem.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Text, Skeleton, useTheme } from '@fun-eat/design-system'; -import { memo, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; -import PBPreviewImage from '@/assets/samgakgimbab.svg'; -import { SvgIcon } from '@/components/Common'; -import { CATEGORY_TYPE } from '@/constants'; -import type { Product } from '@/types/product'; - -interface ProductItemProps { - product: Product; -} - -const ProductItem = ({ product }: ProductItemProps) => { - const theme = useTheme(); - const { category } = useParams(); - const { name, price, image, averageRating, reviewCount } = product; - const [isImageLoading, setIsImageLoading] = useState(true); - - return ( - - {image ? ( - <> - setIsImageLoading(false)} - /> - {isImageLoading && } - - ) : category === CATEGORY_TYPE.FOOD ? ( - - ) : ( - - )} - - - {name} - - - {price.toLocaleString('ko-KR')}원 - - - - - - {averageRating.toFixed(1)} - - - - - - {reviewCount} - - - - - - ); -}; - -export default memo(ProductItem); - -const ProductItemContainer = styled.div` - position: relative; - display: flex; - align-items: center; - padding: 12px 0; -`; - -const ProductImage = styled.img` - object-fit: cover; -`; - -const ProductInfoWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - margin-left: 30px; -`; - -const ProductReviewWrapper = styled.div` - display: flex; - margin-left: -2px; - column-gap: 20px; -`; - -const RatingIconWrapper = styled.div` - display: flex; - align-items: center; - column-gap: 4px; - - & > svg { - padding-bottom: 2px; - } -`; - -const ReviewIconWrapper = styled.div` - display: flex; - align-items: center; - column-gap: 4px; - - & > svg { - padding-top: 2px; - } -`; diff --git a/frontend/src/components/Product/ProductList/ProductList.stories.tsx b/frontend/src/components/Product/ProductList/ProductList.stories.tsx deleted file mode 100644 index de89073b..00000000 --- a/frontend/src/components/Product/ProductList/ProductList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductList from './ProductList'; - -const meta: Meta = { - title: 'product/ProductList', - component: ProductList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx deleted file mode 100644 index 2bdaf30e..00000000 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import ProductItem from '../ProductItem/ProductItem'; - -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useCategoryValueContext } from '@/hooks/context'; -import { useInfiniteProductsQuery } from '@/hooks/queries/product'; -import type { CategoryVariant, SortOption } from '@/types/common'; - -interface ProductListProps { - category: CategoryVariant; - selectedOption?: SortOption; -} - -const ProductList = ({ category, selectedOption }: ProductListProps) => { - const scrollRef = useRef(null); - const { categoryIds } = useCategoryValueContext(); - - const { fetchNextPage, hasNextPage, data } = useInfiniteProductsQuery( - categoryIds[category], - selectedOption?.value ?? 'reviewCount,desc' - ); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const productList = data.pages.flatMap((page) => page.products); - - return ( - <> - - {productList.map((product) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default ProductList; - -const ProductListContainer = styled.ul` - display: flex; - flex-direction: column; - - & > li { - border-bottom: 1px solid ${({ theme }) => theme.borderColors.disabled}; - } -`; diff --git a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx b/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx deleted file mode 100644 index 4dc5590b..00000000 --- a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductOverviewItem from './ProductOverviewItem'; - -const meta: Meta = { - title: 'product/ProductOverviewItem', - component: ProductOverviewItem, - args: { - image: 'https://t3.ftcdn.net/jpg/06/06/91/70/240_F_606917032_4ujrrMV8nspZDX8nTgGrTpJ69N9JNxOL.jpg', - name: '소금빵', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const Ranking: Story = { - args: { - rank: 1, - }, -}; diff --git a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx b/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx deleted file mode 100644 index ef281107..00000000 --- a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Text } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; - -interface ProductOverviewItemProps { - name: string; - image: string | null; - rank?: number; -} - -const ProductOverviewItem = ({ name, image, rank }: ProductOverviewItemProps) => { - return ( - - - {rank ?? ''} - - {image !== null ? ( - - ) : ( - - )} - - {name} - - - ); -}; - -export default ProductOverviewItem; - -const ProductOverviewContainer = styled.div>` - display: flex; - gap: 15px; - align-items: center; - height: 50px; - padding: 0 15px; - border-radius: ${({ theme }) => theme.borderRadius.xs}; - background: ${({ theme, rank }) => (rank ? theme.colors.gray1 : theme.colors.white)}; -`; - -const ProductOverviewImage = styled.img` - width: 45px; - height: 45px; - border-radius: 50%; -`; - -const ProductPreviewImage = styled(PreviewImage)` - width: 45px; - height: 45px; - border-radius: 50%; - background-color: ${({ theme }) => theme.colors.white}; -`; - -const ProductOverviewText = styled(Text)` - white-space: nowrap; - text-overflow: ellipsis; - word-break: break-all; - overflow: hidden; -`; diff --git a/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx b/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx deleted file mode 100644 index 7298f3c8..00000000 --- a/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Link, Text } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { RecipeItem } from '@/components/Recipe'; -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteProductRecipesQuery } from '@/hooks/queries/product'; -import type { SortOption } from '@/types/common'; - -interface ProductRecipeListProps { - productId: number; - productName: string; - selectedOption: SortOption; -} - -const ProductRecipeList = ({ productId, productName, selectedOption }: ProductRecipeListProps) => { - const scrollRef = useRef(null); - const { fetchNextPage, hasNextPage, data } = useInfiniteProductRecipesQuery(productId, selectedOption.value); - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const recipes = data.pages.flatMap((page) => page.recipes); - - if (recipes.length === 0) { - return ( - - - {productName}을/를 {'\n'}사용한 꿀조합을 만들어보세요 🍯 - - - 꿀조합 작성하러 가기 - - - ); - } - - return ( - <> - - {recipes.map((recipe) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default ProductRecipeList; - -const ProductRecipeListContainer = styled.ul` - & > li + li { - margin-top: 40px; - } -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -const ErrorDescription = styled(Text)` - padding: 20px 0; - white-space: pre-wrap; -`; - -const RecipeLink = styled(Link)` - padding: 16px 24px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx b/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx deleted file mode 100644 index fbd2b1ed..00000000 --- a/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductTitle from './ProductTitle'; - -const meta: Meta = { - title: 'common/ProductTitle', - component: ProductTitle, - args: { - content: '상품 목록', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductTitle/ProductTitle.tsx b/frontend/src/components/Product/ProductTitle/ProductTitle.tsx deleted file mode 100644 index 1c10cf2f..00000000 --- a/frontend/src/components/Product/ProductTitle/ProductTitle.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../../Common/Svg/SvgIcon'; - -import { PATH } from '@/constants/path'; - -interface ProductTitleProps { - content: string; - routeDestination: string; -} - -const ProductTitle = ({ content, routeDestination }: ProductTitleProps) => { - return ( - - - {content} - - - - - - - ); -}; - -export default ProductTitle; - -const ProductTitleContainer = styled.div` - position: relative; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: 30px; -`; - -const ProductTitleLink = styled(Link)` - display: flex; - gap: 20px; - align-items: center; - margin-left: 36%; -`; - -const HeadingTitle = styled(Heading)` - font-size: 2.4rem; -`; - -const DropDownIcon = styled(SvgIcon)` - rotate: 270deg; -`; diff --git a/frontend/src/components/Product/index.ts b/frontend/src/components/Product/index.ts deleted file mode 100644 index 25a06f28..00000000 --- a/frontend/src/components/Product/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as ProductDetailItem } from './ProductDetailItem/ProductDetailItem'; -export { default as ProductItem } from './ProductItem/ProductItem'; -export { default as ProductList } from './ProductList/ProductList'; -export { default as ProductOverviewItem } from './ProductOverviewItem/ProductOverviewItem'; -export { default as ProductRecipeList } from './ProductRecipeList/ProductRecipeList'; -export { default as ProductTitle } from './ProductTitle/ProductTitle'; diff --git a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx b/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx deleted file mode 100644 index 68b955e5..00000000 --- a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductRankingList from './ProductRankingList'; - -const meta: Meta = { - title: 'product/ProductRankingList', - component: ProductRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx b/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx deleted file mode 100644 index 309f5219..00000000 --- a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Link, Spacing } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; - -import { ProductOverviewItem } from '@/components/Product'; -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useProductRankingQuery } from '@/hooks/queries/rank'; -import displaySlice from '@/utils/displaySlice'; - -interface ProductRankingListProps { - isHomePage?: boolean; -} - -const ProductRankingList = ({ isHomePage = false }: ProductRankingListProps) => { - const { data: productRankings } = useProductRankingQuery(); - const { gaEvent } = useGA(); - const productsToDisplay = displaySlice(isHomePage, productRankings.products, 3); - - const handleProductRankingLinkClick = () => { - gaEvent({ category: 'link', action: '상품 랭킹 링크 클릭', label: '랭킹' }); - }; - - return ( -
      - {productsToDisplay.map(({ id, name, image, categoryType }, index) => ( -
    • - - - - -
    • - ))} -
    - ); -}; - -export default ProductRankingList; diff --git a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx deleted file mode 100644 index c20fa9fa..00000000 --- a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import RecipeRankingItem from './RecipeRankingItem'; - -import mockRecipeRankingList from '@/mocks/data/recipeRankingList.json'; - -const meta: Meta = { - title: 'recipe/RecipeRankingItem', - component: RecipeRankingItem, - args: { - rank: 1, - recipe: mockRecipeRankingList.recipes[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx deleted file mode 100644 index 308f2d3f..00000000 --- a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { Spacing, Text, Skeleton, useTheme } from '@fun-eat/design-system'; -import { useState } from 'react'; -import styled from 'styled-components'; - -import RecipePreviewImage from '@/assets/plate.svg'; -import { SvgIcon } from '@/components/Common'; -import type { RecipeRanking } from '@/types/ranking'; -import { getRelativeDate } from '@/utils/date'; - -interface RecipeRankingItemProps { - rank: number; - recipe: RecipeRanking; -} - -const RecipeRankingItem = ({ rank, recipe }: RecipeRankingItemProps) => { - const theme = useTheme(); - const { - image, - title, - author: { nickname, profileImage }, - favoriteCount, - createdAt, - } = recipe; - const [isImageLoading, setIsImageLoading] = useState(true); - - return ( - - - - - - {image !== null ? ( - <> - setIsImageLoading(false)} - /> - {isImageLoading && } - - ) : ( - - )} - - - {title} - - - - {favoriteCount} - - - - {getRelativeDate(createdAt)} - - - - - - - - {nickname} 님 - - - - - ); -}; - -export default RecipeRankingItem; - -const RecipeRankingItemContainer = styled.div` - width: calc(100% - 50px); - max-width: 560px; - margin: 12px 0; - padding: 0 5px; -`; - -const RecipeRankingWrapper = styled.div` - display: flex; - justify-content: space-between; - width: 95%; -`; - -const RankingRecipeWrapper = styled.div` - display: flex; - align-items: center; -`; - -const RecipeImage = styled.img` - border-radius: 5px; - object-fit: cover; -`; - -const TitleFavoriteWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const FavoriteWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const AuthorWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - height: 100%; -`; - -const AuthorImage = styled.img` - border: 2px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; - object-fit: cover; -`; diff --git a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx deleted file mode 100644 index d13bad6f..00000000 --- a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import RecipeRankingList from './RecipeRankingList'; - -const meta: Meta = { - title: 'recipe/RecipeRankingList', - component: RecipeRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx deleted file mode 100644 index 368bff09..00000000 --- a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Carousel, Link, Text } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; - -import RecipeRankingItem from '../RecipeRankingItem/RecipeRankingItem'; - -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useRecipeRankingQuery } from '@/hooks/queries/rank'; - -const RecipeRankingList = () => { - const { data: recipeResponse } = useRecipeRankingQuery(); - const { gaEvent } = useGA(); - - if (recipeResponse.recipes.length === 0) return 아직 랭킹이 없어요!; - - const handleRecipeRankingLinkClick = () => { - gaEvent({ category: 'link', action: '꿀조합 랭킹 링크 클릭', label: '랭킹' }); - }; - - const carouselList = recipeResponse.recipes.map((recipe, index) => ({ - id: index, - children: ( - - - - ), - })); - - return ; -}; - -export default RecipeRankingList; diff --git a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx b/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx deleted file mode 100644 index 099f3473..00000000 --- a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ReviewRankingItem from './ReviewRankingItem'; - -const meta: Meta = { - title: 'review/ReviewRankingItem', - component: ReviewRankingItem, - args: { - reviewRanking: { - reviewId: 1, - productId: 5, - productName: '구운감자슬림명란마요', - content: - '할머니가 먹을 거 같은 맛입니다. 1960년 전쟁 때 맛 보고 싶었는데 그때는 너무 가난해서 먹을 수 없었는데요 이것보다 긴 리뷰도 잘려 보인답니다', - rating: 4.0, - favoriteCount: 1256, - categoryType: 'food', - createdAt: '2021-08-01T00:00:00.000Z', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx b/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx deleted file mode 100644 index 205cfb03..00000000 --- a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Spacing, Text, useTheme } from '@fun-eat/design-system'; -import { memo } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import type { ReviewRanking } from '@/types/ranking'; -import { getRelativeDate } from '@/utils/date'; - -interface ReviewRankingItemProps { - reviewRanking: ReviewRanking; -} - -const ReviewRankingItem = ({ reviewRanking }: ReviewRankingItemProps) => { - const theme = useTheme(); - - const { productName, content, rating, favoriteCount, createdAt } = reviewRanking; - - return ( - - - {productName} - - - {content} - - - - - - - {favoriteCount} - - - - - - {rating.toFixed(1)} - - - - {getRelativeDate(createdAt)} - - - - ); -}; - -export default memo(ReviewRankingItem); - -const ReviewRankingItemContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px; - border: ${({ theme }) => `1px solid ${theme.borderColors.disabled}`}; - border-radius: ${({ theme }) => theme.borderRadius.sm}; -`; - -const ReviewText = styled(Text)` - display: -webkit-inline-box; - text-overflow: ellipsis; - overflow: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -`; - -const FavoriteStarWrapper = styled.div` - display: flex; - gap: 4px; -`; - -const FavoriteIconWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const RatingIconWrapper = styled.div` - display: flex; - gap: 2px; - align-items: center; - - & > svg { - padding-bottom: 2px; - } -`; - -const ReviewDate = styled(Text)` - margin-left: auto; -`; diff --git a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx b/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx deleted file mode 100644 index 671dd89e..00000000 --- a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ReviewRankingList from './ReviewRankingList'; - -const meta: Meta = { - title: 'review/ReviewRankingList', - component: ReviewRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx b/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx deleted file mode 100644 index 99f10d5f..00000000 --- a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import ReviewRankingItem from '../ReviewRankingItem/ReviewRankingItem'; - -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useReviewRankingQuery } from '@/hooks/queries/rank'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface ReviewRankingListProps { - isHomePage?: boolean; -} - -const ReviewRankingList = ({ isHomePage = false }: ReviewRankingListProps) => { - const { data: reviewRankings } = useReviewRankingQuery(); - const { gaEvent } = useGA(); - const reviewsToDisplay = useDisplaySlice(isHomePage, reviewRankings.reviews); - - const handleReviewRankingLinkClick = () => { - gaEvent({ category: 'link', action: '리뷰 랭킹 링크 클릭', label: '랭킹' }); - }; - - return ( - - {reviewsToDisplay.map((reviewRanking) => ( -
  • - - - -
  • - ))} -
    - ); -}; - -export default ReviewRankingList; - -const ReviewRankingListContainer = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; diff --git a/frontend/src/components/Rank/index.ts b/frontend/src/components/Rank/index.ts deleted file mode 100644 index 1b39a108..00000000 --- a/frontend/src/components/Rank/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as ReviewRankingItem } from '../Rank/ReviewRankingItem/ReviewRankingItem'; -export { default as ReviewRankingList } from './ReviewRankingList/ReviewRankingList'; -export { default as ProductRankingList } from './ProductRankingList/ProductRankingList'; -export { default as RecipeRankingItem } from './RecipeRankingItem/RecipeRankingItem'; -export { default as RecipeRankingList } from './RecipeRankingList/RecipeRankingList'; diff --git a/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx b/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx deleted file mode 100644 index e65b8722..00000000 --- a/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentForm from './CommentForm'; - -const meta: Meta = { - title: 'recipe/CommentForm', - component: CommentForm, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx b/frontend/src/components/Recipe/CommentForm/CommentForm.tsx deleted file mode 100644 index 5eb0ac1c..00000000 --- a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Button, Spacing, Text, Textarea, useTheme, useToastActionContext } from '@fun-eat/design-system'; -import type { ChangeEventHandler, FormEventHandler, RefObject } from 'react'; -import { useState } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useScroll } from '@/hooks/common'; -import { useRecipeCommentMutation } from '@/hooks/queries/recipe'; - -interface CommentFormProps { - recipeId: number; - scrollTargetRef: RefObject; -} - -const MAX_COMMENT_LENGTH = 200; - -const CommentForm = ({ recipeId, scrollTargetRef }: CommentFormProps) => { - const [commentValue, setCommentValue] = useState(''); - const { mutate } = useRecipeCommentMutation(recipeId); - - const theme = useTheme(); - const { toast } = useToastActionContext(); - - const { scrollToPosition } = useScroll(); - - const handleCommentInput: ChangeEventHandler = (e) => { - setCommentValue(e.target.value); - }; - - const handleSubmitComment: FormEventHandler = (e) => { - e.preventDefault(); - - mutate( - { comment: commentValue }, - { - onSuccess: () => { - setCommentValue(''); - scrollToPosition(scrollTargetRef); - toast.success('댓글이 등록되었습니다.'); - }, - onError: (error) => { - if (error instanceof Error) { - toast.error(error.message); - return; - } - - toast.error('댓글을 등록하는데 오류가 발생했습니다.'); - }, - } - ); - }; - - return ( - -
    - - - - - - - - {commentValue.length}자 / {MAX_COMMENT_LENGTH}자 - -
    - ); -}; - -export default CommentForm; - -const CommentFormContainer = styled.div` - position: fixed; - bottom: 0; - width: calc(100% - 40px); - max-width: 540px; - padding: 16px 0; - background: ${({ theme }) => theme.backgroundColors.default}; -`; - -const Form = styled.form` - display: flex; - gap: 4px; - justify-content: space-around; - align-items: center; -`; - -const CommentTextarea = styled(Textarea)` - height: 50px; - padding: 8px; - font-size: 1.4rem; -`; - -const SubmitButton = styled(Button)` - cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; -`; diff --git a/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx b/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx deleted file mode 100644 index 70bf1f9a..00000000 --- a/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentItem from './CommentItem'; - -import comments from '@/mocks/data/comments.json'; - -const meta: Meta = { - title: 'recipe/CommentItem', - component: CommentItem, - args: { - recipeComment: comments.comments[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentItem/CommentItem.tsx b/frontend/src/components/Recipe/CommentItem/CommentItem.tsx deleted file mode 100644 index 847194b7..00000000 --- a/frontend/src/components/Recipe/CommentItem/CommentItem.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Divider, Spacing, Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { Comment } from '@/types/recipe'; -import { getFormattedDate } from '@/utils/date'; - -interface CommentItemProps { - recipeComment: Comment; -} - -const CommentItem = ({ recipeComment }: CommentItemProps) => { - const theme = useTheme(); - const { author, comment, createdAt } = recipeComment; - - return ( - <> - - -
    - - {author.nickname} 님 - - - {getFormattedDate(createdAt)} - -
    -
    - {comment} - - - - ); -}; - -export default CommentItem; - -const AuthorWrapper = styled.div` - display: flex; - gap: 12px; - align-items: center; -`; - -const AuthorProfileImage = styled.img` - border: 1px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; -`; - -const CommentContent = styled(Text)` - margin: 16px 0; -`; diff --git a/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx b/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx deleted file mode 100644 index ebad218d..00000000 --- a/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentList from './CommentList'; - -const meta: Meta = { - title: 'recipe/CommentList', - component: CommentList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentList/CommentList.tsx b/frontend/src/components/Recipe/CommentList/CommentList.tsx deleted file mode 100644 index d44f33c3..00000000 --- a/frontend/src/components/Recipe/CommentList/CommentList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Heading, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; - -import CommentItem from '../CommentItem/CommentItem'; - -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteRecipeCommentQuery } from '@/hooks/queries/recipe'; - -interface CommentListProps { - recipeId: number; -} - -const CommentList = ({ recipeId }: CommentListProps) => { - const scrollRef = useRef(null); - - const { fetchNextPage, hasNextPage, data } = useInfiniteRecipeCommentQuery(Number(recipeId)); - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const [{ totalElements }] = data.pages.flatMap((page) => page); - const comments = data.pages.flatMap((page) => page.comments); - - return ( - <> - - 댓글 ({totalElements}개) - - - {totalElements === 0 && 꿀조합의 첫번째 댓글을 달아보세요!} - {comments.map((comment) => ( - - ))} -
    - - ); -}; - -export default CommentList; diff --git a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx b/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx deleted file mode 100644 index eefc6267..00000000 --- a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { RecipeDetailTextarea } from '..'; - -import RecipeFormProvider from '@/contexts/RecipeFormContext'; - -const meta: Meta = { - title: 'recipe/RecipeDetailTextarea', - component: RecipeDetailTextarea, - args: { - recipeDetail: '', - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx b/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx deleted file mode 100644 index 79d5cdd4..00000000 --- a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Heading, Spacing, Textarea, Text, useTheme } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -import { useRecipeFormActionContext } from '@/hooks/context'; - -const MAX_LENGTH = 500; - -interface RecipeDetailTextareaProps { - recipeDetail: string; -} - -const RecipeDetailTextarea = ({ recipeDetail }: RecipeDetailTextareaProps) => { - const theme = useTheme(); - - const { handleRecipeFormValue } = useRecipeFormActionContext(); - - const handleRecipeDetail: ChangeEventHandler = (e) => { - handleRecipeFormValue({ target: 'content', value: e.currentTarget.value }); - }; - - return ( - <> - - 자세한 설명을 남겨주세요. - * - - -