From fbcc768d1c365291331fe70ba03e426776bc71d0 Mon Sep 17 00:00:00 2001 From: Jeremy <102432453+shackstack@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:02:51 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=8B=AC=20celuveat=20UI?= =?UTF-8?q?=20library=20=EC=A0=81=EC=9A=A9=20(#664)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 모달 라이브러리 적용 (jeremy-reusable-modal) * style: isLiked시 하트 색깔 변경 로직 줄 축수 * refactor: jeremy-reusable-modal에서 celuveat-ui-library로 마이그레이션 --- frontend/.vscode/settings.json | 4 +- .../src/components/@common/Dialog/Dialog.tsx | 70 + .../src/components/@common/Dialog/index.tsx | 3 + .../ProfileImageList/ProfileImageList.tsx | 1 - .../Form/SuggestionForm/SuggestionForm.tsx | 119 + .../components/Form/SuggestionForm/index.tsx | 3 + .../src/components/FormModal/FormModal.tsx | 50 + frontend/src/components/FormModal/index.tsx | 3 + .../components/InfoDropDown/InfoDropDown.tsx | 63 +- .../LoginModal/LoginModal.stories.tsx | 7 +- .../src/components/LoginModal/LoginModal.tsx | 13 +- .../MiniRestaurantCard/MiniRestaurantCard.tsx | 75 +- .../RestaurantCard/RestaurantCard.tsx | 61 +- .../RestaurantDetailLikeButton.tsx | 24 +- .../RestaurantReviewItem.tsx | 20 +- .../RestaurantReviewList.stories.tsx | 158 +- .../RestaurantReviewList.tsx | 35 +- .../RestaurantReviewWrapper.tsx | 45 +- .../RestaurantWishItem/RestaurantWishItem.tsx | 47 +- .../ReviewForm/ReviewDeleteForm.tsx | 8 +- .../src/components/ReviewForm/ReviewForm.tsx | 6 +- .../ReviewForm/ReviewReportForm.tsx | 8 +- .../components/ReviewModal/ReviewModal.tsx | 40 - frontend/src/components/ReviewModal/index.tsx | 3 - .../SuggestionButton/SuggestionButton.tsx | 125 +- .../src/hooks/server/useRestaurantReview.ts | 4 +- .../hooks/server/useToggleLikeNotUpdate.ts | 8 +- frontend/src/hooks/useCeluveatModal.tsx | 32 + .../RestaurantDetailPage/RestaurantDetail.tsx | 2 + frontend/yarn.lock | 3720 ++++++----------- 30 files changed, 1649 insertions(+), 3108 deletions(-) create mode 100644 frontend/src/components/@common/Dialog/Dialog.tsx create mode 100644 frontend/src/components/@common/Dialog/index.tsx create mode 100644 frontend/src/components/Form/SuggestionForm/SuggestionForm.tsx create mode 100644 frontend/src/components/Form/SuggestionForm/index.tsx create mode 100644 frontend/src/components/FormModal/FormModal.tsx create mode 100644 frontend/src/components/FormModal/index.tsx delete mode 100644 frontend/src/components/ReviewModal/ReviewModal.tsx delete mode 100644 frontend/src/components/ReviewModal/index.tsx create mode 100644 frontend/src/hooks/useCeluveatModal.tsx diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index 688d3cb70..8194859ba 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -1,8 +1,8 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.fixAll.stylelint": true + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" }, "typescript.validate.enable": true, diff --git a/frontend/src/components/@common/Dialog/Dialog.tsx b/frontend/src/components/@common/Dialog/Dialog.tsx new file mode 100644 index 000000000..fabf6278d --- /dev/null +++ b/frontend/src/components/@common/Dialog/Dialog.tsx @@ -0,0 +1,70 @@ +import { Modal } from 'celuveat-ui-library'; +import React, { ReactNode } from 'react'; +import styled from 'styled-components'; +import Exit from '~/assets/icons/exit.svg'; +import useCeluveatModal from '~/hooks/useCeluveatModal'; + +interface Props { + title?: string; + children: ReactNode; +} + +function Dialog({ title, children }: Props) { + const { closeModal } = useCeluveatModal(); + + return ( + <> + } /> + + {title} + + {children} + + + ); +} + +export default Dialog; + +const StyledOverlay = styled.div` + position: absolute; + top: 0; + + width: 100%; + height: 100%; + + background-color: black; + + opacity: 0.2; +`; + +const StyledContent = styled.div` + position: fixed; + top: 50%; + left: 50vw; + + min-width: 540px; + max-width: 66.6%; + + padding: 2.4rem; + margin: 0 auto; + + border-radius: 12px; + background-color: white; + + transform: translateX(-50%) translateY(-50%); +`; + +const StyledTitle = styled.h4` + text-align: center; + + margin-bottom: 2.4rem; +`; + +const StyledExitButton = styled(Exit)` + position: absolute; + top: 12px; + right: 12px; + + cursor: pointer; +`; diff --git a/frontend/src/components/@common/Dialog/index.tsx b/frontend/src/components/@common/Dialog/index.tsx new file mode 100644 index 000000000..d0a54d2ee --- /dev/null +++ b/frontend/src/components/@common/Dialog/index.tsx @@ -0,0 +1,3 @@ +import Dialog from './Dialog'; + +export default Dialog; diff --git a/frontend/src/components/@common/ProfileImageList/ProfileImageList.tsx b/frontend/src/components/@common/ProfileImageList/ProfileImageList.tsx index c89c47a1c..5e6539b54 100644 --- a/frontend/src/components/@common/ProfileImageList/ProfileImageList.tsx +++ b/frontend/src/components/@common/ProfileImageList/ProfileImageList.tsx @@ -46,7 +46,6 @@ const StyledProfileImageList = styled.div<{ size: string }>` const StyledProfileImageWrapper = styled.div<{ index: number; hover: boolean; length: number }>` position: absolute; - z-index: ${({ length, index }) => 10 + length - index}; transition: 0.4s ease-in-out; diff --git a/frontend/src/components/Form/SuggestionForm/SuggestionForm.tsx b/frontend/src/components/Form/SuggestionForm/SuggestionForm.tsx new file mode 100644 index 000000000..61c20d25c --- /dev/null +++ b/frontend/src/components/Form/SuggestionForm/SuggestionForm.tsx @@ -0,0 +1,119 @@ +import { ChangeEvent, FormEvent, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import styled from 'styled-components'; +import { postRevisedInfo } from '~/api/restaurant'; +import TextButton from '~/components/@common/Button'; +import Dialog from '~/components/@common/Dialog'; +import { BORDER_RADIUS, FONT_SIZE } from '~/styles/common'; + +const labels = [ + '레스토랑이 폐점했어요.', + '레스토랑 이름이 잘못되었어요.', + '레스토랑 주소가 잘못되었어요.', + '레스토랑 전화번호가 잘못되었어요', +]; + +function SuggestionForm() { + const { id } = useParams(); + const [checkedItems, setCheckedItems] = useState([]); + const [textareaValue, setTextareaValue] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + + const requestData = textareaValue ? [...checkedItems, textareaValue] : checkedItems; + postRevisedInfo({ restaurantId: Number(id), data: { contents: requestData } }); + window.location.reload(); + }; + + const clickCheckBox = (e: ChangeEvent) => { + const { checked, value } = e.target; + + setCheckedItems(prev => (checked ? [...prev, value] : prev.filter(item => item !== value))); + }; + + const onChangeTextarea = (e: ChangeEvent) => { + setTextareaValue(e.target.value); + }; + + return ( + + +
수정 항목
+

잘못되었거나 수정이 필요한 정보들을 모두 선택해주세요.

+ + {labels.map(label => ( + + + {label} + + ))} + + + +
+
+ ); +} + +export default SuggestionForm; + +const StyledForm = styled.form` + display: flex; + flex-direction: column; + gap: 2rem; + + font-size: ${FONT_SIZE.md}; + + p { + color: var(--gray-3); + } +`; + +const StyledUnorderedList = styled.ul` + display: flex; + flex-direction: column; + gap: 2rem; +`; + +const StyledListItem = styled.li` + display: flex; + align-items: center; + gap: 0.8rem; +`; + +const StyledTextarea = styled.textarea` + height: 128px; + + padding: 0.8rem; + + border: none; + border-radius: ${BORDER_RADIUS.sm}; + background-color: var(--gray-1); +`; + +const CheckBox = styled.input.attrs({ type: 'checkbox' })` + width: 24px; + height: 24px; + + border: 1px solid #ddd; + border-radius: 50%; + background-image: url('~/assets/icons/unchecked-icon.svg'); + background-size: cover; + + transition: all 0.2s ease; + appearance: none; + + &:checked { + border: 1px solid var(--primary-6); + background-color: var(--primary-6); + background-image: url('~/assets/icons/checked-icon.svg'); + background-size: cover; + } +`; diff --git a/frontend/src/components/Form/SuggestionForm/index.tsx b/frontend/src/components/Form/SuggestionForm/index.tsx new file mode 100644 index 000000000..afb5f5567 --- /dev/null +++ b/frontend/src/components/Form/SuggestionForm/index.tsx @@ -0,0 +1,3 @@ +import SuggestionForm from './SuggestionForm'; + +export default SuggestionForm; diff --git a/frontend/src/components/FormModal/FormModal.tsx b/frontend/src/components/FormModal/FormModal.tsx new file mode 100644 index 000000000..ba65c308d --- /dev/null +++ b/frontend/src/components/FormModal/FormModal.tsx @@ -0,0 +1,50 @@ +import Dialog from '../@common/Dialog'; +import RestaurantReviewList from '../RestaurantReviewList/RestaurantReviewList'; +import { ReviewDeleteForm, ReviewForm, ReviewReportForm } from '../ReviewForm'; + +const REVIEW_FORM_TITLE = { + create: '리뷰 작성하기', + update: '리뷰 수정하기', + delete: '리뷰 삭제하기', + report: '리뷰 신고하기', + all: '리뷰 모두 보기', +} as const; + +interface FormModalProps { + type: keyof typeof REVIEW_FORM_TITLE; + reviewId?: number; +} + +function FormModal({ type, reviewId }: FormModalProps) { + return ( +
+ {type === 'create' && ( + + + + )} + {type === 'update' && ( + + + + )} + {type === 'delete' && ( + + + + )} + {type === 'report' && ( + + + + )} + {type === 'all' && ( + + + + )} +
+ ); +} + +export default FormModal; diff --git a/frontend/src/components/FormModal/index.tsx b/frontend/src/components/FormModal/index.tsx new file mode 100644 index 000000000..9028427d0 --- /dev/null +++ b/frontend/src/components/FormModal/index.tsx @@ -0,0 +1,3 @@ +import FormModal from '~/components/FormModal/FormModal'; + +export default FormModal; diff --git a/frontend/src/components/InfoDropDown/InfoDropDown.tsx b/frontend/src/components/InfoDropDown/InfoDropDown.tsx index ca35d1ead..e33bd933a 100644 --- a/frontend/src/components/InfoDropDown/InfoDropDown.tsx +++ b/frontend/src/components/InfoDropDown/InfoDropDown.tsx @@ -4,13 +4,12 @@ import { useNavigate } from 'react-router-dom'; import { styled } from 'styled-components'; import { getProfile } from '~/api/user'; import InfoButton from '~/components/@common/InfoButton'; -import LoginModal from '~/components/LoginModal'; -import useBooleanState from '~/hooks/useBooleanState'; import { ProfileData } from '~/@types/api.types'; +import useCeluveatModal from '~/hooks/useCeluveatModal'; function InfoDropDown() { - const { value: isModalOpen, setTrue: openModal, setFalse: closeModal } = useBooleanState(false); + const { openLoginModal } = useCeluveatModal(); const { data, isSuccess: isLogin } = useQuery({ queryKey: ['profile'], queryFn: getProfile, @@ -31,38 +30,34 @@ function InfoDropDown() { }; return ( - <> - - - - - - - - - {isLogin ? ( - <> - - 마이 페이지 - - - 위시리스트 - - - 회원 탈퇴 - - - ) : ( - - 로그인 + + + + + + + + + {isLogin ? ( + <> + + 마이 페이지 - )} - - - - - - + + 위시리스트 + + + 회원 탈퇴 + + + ) : ( + + 로그인 + + )} + + + ); } diff --git a/frontend/src/components/LoginModal/LoginModal.stories.tsx b/frontend/src/components/LoginModal/LoginModal.stories.tsx index 62c004311..44b52f9b0 100644 --- a/frontend/src/components/LoginModal/LoginModal.stories.tsx +++ b/frontend/src/components/LoginModal/LoginModal.stories.tsx @@ -20,9 +20,4 @@ export default meta; type Story = StoryObj; -export const Default: Story = { - args: { - isOpen: true, - close: () => {}, - }, -}; +export const Default: Story = {}; diff --git a/frontend/src/components/LoginModal/LoginModal.tsx b/frontend/src/components/LoginModal/LoginModal.tsx index 9c0b46847..6b7e93319 100644 --- a/frontend/src/components/LoginModal/LoginModal.tsx +++ b/frontend/src/components/LoginModal/LoginModal.tsx @@ -1,20 +1,15 @@ import { styled } from 'styled-components'; import LoginButton from '~/components/@common/LoginButton'; -import Modal from '../@common/Modal'; +import Dialog from '../@common/Dialog'; -interface LoginModalProps { - isOpen: boolean; - close: VoidFunction; -} - -function LoginModal({ isOpen, close }: LoginModalProps) { +function LoginModal() { return ( - + - + ); } diff --git a/frontend/src/components/MiniRestaurantCard/MiniRestaurantCard.tsx b/frontend/src/components/MiniRestaurantCard/MiniRestaurantCard.tsx index ae5e251ea..ddb534016 100644 --- a/frontend/src/components/MiniRestaurantCard/MiniRestaurantCard.tsx +++ b/frontend/src/components/MiniRestaurantCard/MiniRestaurantCard.tsx @@ -7,7 +7,6 @@ import Star from '~/assets/icons/star.svg'; import type { Celeb } from '~/@types/celeb.types'; import type { Restaurant } from '~/@types/restaurant.types'; -import LoginModal from '~/components/LoginModal'; import useToggleLikeNotUpdate from '~/hooks/server/useToggleLikeNotUpdate'; import WaterMarkImage from '../@common/WaterMarkImage'; import ProfileImageList from '../@common/ProfileImageList'; @@ -36,7 +35,7 @@ function MiniRestaurantCard({ hideProfile = false, }: MiniRestaurantCardProps) { const { id, images, name, roadAddress, category, rating, distance } = restaurant; - const { isModalOpen, closeModal, toggleRestaurantLike, isLiked } = useToggleLikeNotUpdate(restaurant); + const { toggleRestaurantLike, isLiked } = useToggleLikeNotUpdate(restaurant); const navigate = useNavigate(); @@ -51,45 +50,41 @@ function MiniRestaurantCard({ }; return ( - <> - - - - {showLike && ( - - + + + + {showLike && ( + + + )} + + + + {name} + {showRating && ( + + {rating.toFixed(2)} + )} - - - - {name} - {showRating && ( - - {rating.toFixed(2)} - - )} - - {category} - {roadAddress} - {showDistance && {distance}m 이내} - {!hideProfile && ( - {celebs && } - )} - - - - - + + {category} + {roadAddress} + {showDistance && {distance}m 이내} + {!hideProfile && ( + {celebs && } + )} + + ); } diff --git a/frontend/src/components/RestaurantCard/RestaurantCard.tsx b/frontend/src/components/RestaurantCard/RestaurantCard.tsx index 238d60424..c560a2648 100644 --- a/frontend/src/components/RestaurantCard/RestaurantCard.tsx +++ b/frontend/src/components/RestaurantCard/RestaurantCard.tsx @@ -8,7 +8,6 @@ import { FONT_SIZE, truncateText } from '~/styles/common'; import type { Celeb } from '~/@types/celeb.types'; import type { Restaurant } from '~/@types/restaurant.types'; -import LoginModal from '~/components/LoginModal'; import useToggleLikeNotUpdate from '~/hooks/server/useToggleLikeNotUpdate'; interface RestaurantCardProps { @@ -21,7 +20,7 @@ interface RestaurantCardProps { function RestaurantCard({ restaurant, celebs, size, type = 'list', setHoveredId = () => {} }: RestaurantCardProps) { const { id, images, name, roadAddress, category, phoneNumber, likeCount } = restaurant; - const { isModalOpen, closeModal, toggleRestaurantLike, isLiked } = useToggleLikeNotUpdate(restaurant); + const { toggleRestaurantLike, isLiked } = useToggleLikeNotUpdate(restaurant); const navigate = useNavigate(); @@ -36,37 +35,33 @@ function RestaurantCard({ restaurant, celebs, size, type = 'list', setHoveredId }; return ( - <> - - - - - - - - - {category} - {name} - {roadAddress} - {phoneNumber} - - - 좋아요 {likeCount.toLocaleString()}개 - {celebs && } - - - - - - + + + + + + + + + {category} + {name} + {roadAddress} + {phoneNumber} + + + 좋아요 {likeCount.toLocaleString()}개 + {celebs && } + + + ); } diff --git a/frontend/src/components/RestaurantDetailLikeButton/RestaurantDetailLikeButton.tsx b/frontend/src/components/RestaurantDetailLikeButton/RestaurantDetailLikeButton.tsx index 4ecbc444b..086f108ea 100644 --- a/frontend/src/components/RestaurantDetailLikeButton/RestaurantDetailLikeButton.tsx +++ b/frontend/src/components/RestaurantDetailLikeButton/RestaurantDetailLikeButton.tsx @@ -1,6 +1,5 @@ import { useQuery } from '@tanstack/react-query'; import useToggleLikeNotUpdate from '~/hooks/server/useToggleLikeNotUpdate'; -import LoginModal from '~/components/LoginModal'; import WhiteLove from '~/assets/icons/love.svg'; import { RestaurantData } from '~/@types/api.types'; import { getRestaurantDetail } from '~/api/restaurant'; @@ -20,26 +19,13 @@ function RestaurantDetailLikeButton({ showText = true, restaurantId, celebId }: suspense: true, }); - const { isModalOpen, isLiked, closeModal, toggleRestaurantLike } = useToggleLikeNotUpdate(restaurant); + const { isLiked, toggleRestaurantLike } = useToggleLikeNotUpdate(restaurant); return ( - <> - - - - + ); } diff --git a/frontend/src/components/RestaurantReviewItem/RestaurantReviewItem.tsx b/frontend/src/components/RestaurantReviewItem/RestaurantReviewItem.tsx index f085db2e5..fdaf47cce 100644 --- a/frontend/src/components/RestaurantReviewItem/RestaurantReviewItem.tsx +++ b/frontend/src/components/RestaurantReviewItem/RestaurantReviewItem.tsx @@ -8,7 +8,6 @@ import SpeakerphoneIcon from '~/assets/icons/etc/speakerphone.svg'; import StarRating from '~/components/@common/StarRating'; import ProfileImage from '~/components/@common/ProfileImage'; -import { useReviewModalContext } from '~/hooks/context/ReviewModalProvider'; import useRestaurantReview from '~/hooks/server/useRestaurantReview'; import { FONT_SIZE } from '~/styles/common'; @@ -16,13 +15,13 @@ import { getProfile } from '~/api/user'; import { getReviewImgUrl, lookImage } from '~/utils/image'; import type { ProfileData, RestaurantReview } from '~/@types/api.types'; +import useCeluveatModal from '~/hooks/useCeluveatModal'; interface RestaurantReviewItemProps { review: RestaurantReview; - isInModal: boolean; } -function RestaurantReviewItem({ review, isInModal }: RestaurantReviewItemProps) { +function RestaurantReviewItem({ review }: RestaurantReviewItemProps) { const { data: profileData } = useQuery({ queryKey: ['profile'], queryFn: getProfile, @@ -30,7 +29,7 @@ function RestaurantReviewItem({ review, isInModal }: RestaurantReviewItemProps) const { getReviewIsLiked, toggleRestaurantReviewLike } = useRestaurantReview(); - const { openReviewModal, setReviewId } = useReviewModalContext(); + const { openReviewFormModal } = useCeluveatModal(); const isUsersReview = profileData?.memberId === review.memberId; @@ -50,8 +49,7 @@ function RestaurantReviewItem({ review, isInModal }: RestaurantReviewItemProps)