From d61364a329b6b305f121ff2f89e979a2c6830fde Mon Sep 17 00:00:00 2001 From: "songhj10207@naver.com" Date: Sun, 17 Nov 2024 12:31:20 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EB=94=94=ED=85=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 3 +- .../common/bookmark/BookmarkContainer.jsx | 7 +- .../common/button/LocationButton.jsx | 34 ++++ src/components/common/footer/Footer.jsx | 2 +- src/components/common/header/Header.jsx | 3 +- src/components/common/index.js | 18 +- src/components/common/keyword/Keyword.jsx | 52 ++++++ src/components/storeDetail/DetailBox.jsx | 57 ++++++ src/components/storeDetail/PieChart.jsx | 87 +++++++++ src/components/storeDetail/PopOver.jsx | 58 ++++++ src/components/storeDetail/ReviewDetail.jsx | 70 ++++++++ src/components/storeDetail/StoreInfoCard.jsx | 43 +++++ src/components/storeDetail/StoreInsight.jsx | 82 +++++++++ src/components/storeDetail/StoreMap.jsx | 62 +++++++ src/components/storeDetail/StoreOverview.jsx | 45 +++++ src/components/storeDetail/StorePreview.jsx | 166 ++++++++++++++++++ src/components/storeDetail/StoreTip.jsx | 61 +++++++ src/components/storeDetail/index.js | 7 + src/pages/DetailPage.jsx | 160 +++++++++++++++++ src/pages/StoreDetailPage.jsx | 1 + 20 files changed, 1010 insertions(+), 8 deletions(-) create mode 100644 src/components/common/button/LocationButton.jsx create mode 100644 src/components/common/keyword/Keyword.jsx create mode 100644 src/components/storeDetail/DetailBox.jsx create mode 100644 src/components/storeDetail/PieChart.jsx create mode 100644 src/components/storeDetail/PopOver.jsx create mode 100644 src/components/storeDetail/ReviewDetail.jsx create mode 100644 src/components/storeDetail/StoreInfoCard.jsx create mode 100644 src/components/storeDetail/StoreInsight.jsx create mode 100644 src/components/storeDetail/StoreMap.jsx create mode 100644 src/components/storeDetail/StoreOverview.jsx create mode 100644 src/components/storeDetail/StorePreview.jsx create mode 100644 src/components/storeDetail/StoreTip.jsx create mode 100644 src/components/storeDetail/index.js create mode 100644 src/pages/DetailPage.jsx diff --git a/src/App.jsx b/src/App.jsx index f103d3c..5a94857 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import { MainPage, WebMap, StoreDetailPage, SignUpPage, LoginPage } from './page import './App.css'; import { HeaderLayout } from './components/common'; import BookmarkPage from './pages/BookmarkPage'; +import DetailPage from './pages/DetailPage'; function App() { return ( @@ -17,10 +18,10 @@ function App() { } /> } /> } /> + } /> } /> } /> - } /> diff --git a/src/components/common/bookmark/BookmarkContainer.jsx b/src/components/common/bookmark/BookmarkContainer.jsx index 3651e9a..d15ec5b 100644 --- a/src/components/common/bookmark/BookmarkContainer.jsx +++ b/src/components/common/bookmark/BookmarkContainer.jsx @@ -4,13 +4,13 @@ import { deleteBookmarkStore, postBookmarkStore } from '../../../apis/api/bookma import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { useSaveBookmarkId } from '../../../store'; -import { useState } from 'react'; const BookmarkContainer = ({ bookmarkId, storeId }) => { const { savedId } = useSaveBookmarkId(); const auth = JSON.parse(localStorage.getItem('auth')) || {}; - const [isSaved, setIsSaved] = useState(savedId.includes(bookmarkId) && auth.state.isLoggedIn); + const isSaved = savedId.includes(bookmarkId) && auth.state.isLoggedIn; + const navigate = useNavigate(); const handleClickBookmarks = async (e) => { @@ -24,12 +24,11 @@ const BookmarkContainer = ({ bookmarkId, storeId }) => { const response = await deleteBookmarkStore(bookmarkId); if (response.status === 204) { console.log('success delete'); - setIsSaved(false); } } else { const response = await postBookmarkStore(storeId); if (response.status === 201) { - setIsSaved(true); + navigate('/bookmark'); } else { console.log(response.error); } diff --git a/src/components/common/button/LocationButton.jsx b/src/components/common/button/LocationButton.jsx new file mode 100644 index 0000000..adcb8cf --- /dev/null +++ b/src/components/common/button/LocationButton.jsx @@ -0,0 +1,34 @@ +import styled from 'styled-components'; +import { White, Orange, DarkGrey } from '../../../color'; +import { ReactComponent as Path } from '../../../assets/Icon/detail/Path.svg'; + +const LocationButton = ({ pathClickHandler }) => { + return ( + + + 길찾기 + + ); +}; + +export default LocationButton; + +const StyledButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + color: ${Orange}; + gap: 5px; + width: 80px; + height: 35px; + font-size: 16px; + font-weight: 600; + border-radius: 10px; + border: 1px solid ${Orange}; + background-color: ${White}; + + & > svg { + width: 17px; + } + cursor: pointer; +`; diff --git a/src/components/common/footer/Footer.jsx b/src/components/common/footer/Footer.jsx index 4a885d5..febd5c9 100644 --- a/src/components/common/footer/Footer.jsx +++ b/src/components/common/footer/Footer.jsx @@ -10,7 +10,7 @@ const Footer = () => {

맛알고리즘은 AI가 다수의 고객 리뷰를 정밀히 분석하여 숨겨진 인사이트를 찾아주는 서비스입니다.

- Email : gmail@gmail.com + Email : aktdkfrhflwma@gmail.com

diff --git a/src/components/common/header/Header.jsx b/src/components/common/header/Header.jsx index f34bbcb..34d4e80 100644 --- a/src/components/common/header/Header.jsx +++ b/src/components/common/header/Header.jsx @@ -5,7 +5,7 @@ import Button from '../button/Button'; import { ReactComponent as Logo } from '../../../assets/Icon/Logo.svg'; import { logout } from '../../../apis/api/login'; import Swal from 'sweetalert2'; -import { LightGrey } from '../../../color'; +import { LightGrey, White } from '../../../color'; const Header = () => { const { isLoggedIn, setLogout } = useLogin(); @@ -61,6 +61,7 @@ const HeaderLayout = styled.header` display: flex; justify-content: space-between; padding-right: 100px; + background-color: ${White}; padding-left: 50px; & > svg { width: 250px; diff --git a/src/components/common/index.js b/src/components/common/index.js index 0ebb201..b2f5c98 100644 --- a/src/components/common/index.js +++ b/src/components/common/index.js @@ -9,4 +9,20 @@ import Button from './button/Button'; import Header from './header/Header'; import HeaderLayout from './header/HeaderLayout'; import FormField from './form/FormField'; -export { Category, MyMap, StoreList, StoreCard, Filtering, Footer, Button, SearchBar, Header, HeaderLayout, FormField }; +import LocationButton from './button/LocationButton'; +import Keyword from './keyword/Keyword'; +export { + Category, + MyMap, + StoreList, + StoreCard, + Filtering, + Footer, + Button, + SearchBar, + Header, + HeaderLayout, + FormField, + LocationButton, + Keyword, +}; diff --git a/src/components/common/keyword/Keyword.jsx b/src/components/common/keyword/Keyword.jsx new file mode 100644 index 0000000..16b6a19 --- /dev/null +++ b/src/components/common/keyword/Keyword.jsx @@ -0,0 +1,52 @@ +import styled from 'styled-components'; +import { DarkGreen, Orange, White } from '../../../color'; + +const Keyword = ({ keyword, type }) => { + if (!keyword) { + return

로딩 중...

; + } + console.log(keyword); + const keywordItem = keyword.split(', '); + + return ( + + {keywordItem.map((item) => { + return ( + +

#{item}

+
+ ); + })} +
+ ); +}; + +export default Keyword; + +const KeywordBox = styled.div` + width: 100%; + display: flex; + gap: 10px; + flex-direction: row; + align-items: center; +`; + +const KeywordTag = styled.div` + width: fit-content; + padding: 10px; + height: 40px; + display: flex; + border-radius: 50px; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 18px; + + //border: 1px solid ${(props) => (props.type === 'positive' ? `${Orange}` : `${DarkGreen}`)}; + color: ${(props) => (props.type === 'positive' ? `${Orange}` : `${DarkGreen}`)}; + + @media screen and (max-width: 768px) { + font-size: 13px; + padding: 5px; + } +`; diff --git a/src/components/storeDetail/DetailBox.jsx b/src/components/storeDetail/DetailBox.jsx new file mode 100644 index 0000000..3fc469b --- /dev/null +++ b/src/components/storeDetail/DetailBox.jsx @@ -0,0 +1,57 @@ +import React from 'react'; +import styled from 'styled-components'; +import { DarkGrey, Orange, White } from '../../color'; + +const DetailBox = ({ label, content, type }) => { + return ( + + + {type !== 'time' && type !== 'address' &&

{content}

} + {type === 'address' && } + {type === 'time' && ( + + )} +
+ ); +}; + +export default DetailBox; + +const DetailBoxContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; + font-size: 15px; + + & > svg { + width: 16px; + height: 16px; + color: ${DarkGrey}; + } + + & > button { + background-color: ${White}; + font-size: 16px; + display: flex; + justify-content: center; + align-items: center; + &:hover { + cursor: pointer; + } + color: ${(props) => (props.type === 'address' ? `${Orange}` : `${DarkGrey}`)}; + } +`; + +const Time = styled.div` + display: flex; + flex-direction: column; + gap: 10px; +`; + +const Label = styled.div` + width: 70px; + font-size: 16px; + font-weight: 600; +`; diff --git a/src/components/storeDetail/PieChart.jsx b/src/components/storeDetail/PieChart.jsx new file mode 100644 index 0000000..46c4f91 --- /dev/null +++ b/src/components/storeDetail/PieChart.jsx @@ -0,0 +1,87 @@ +import { DarkGrey, Orange } from '../../color'; +import styled from 'styled-components'; + +const PieChart = ({ positiveRatio, neutralRatio, negativeRatio }) => { + const total = positiveRatio + neutralRatio + negativeRatio; + + const positive = (positiveRatio / total) * 100; + const neutral = (neutralRatio / total) * 100; + const negative = (negativeRatio / total) * 100; + + const piechart = [neutral, neutral + negative, 100]; + return ( + + +

+ 긍정 {positive.toFixed(1)}% | 중립 {neutral.toFixed(1)}% | 부정 {negative.toFixed(1)}% +

+ + + + 긍정 + + + + 중립 + + + + 부정 + + +
+ ); +}; + +export default PieChart; + +const PieChartBox = styled.div` + border-radius: 20px; + display: flex; + flex-direction: column; + padding: 20px; + gap: 20px; + margin-bottom: 30px; + + & > p { + font-size: 14px; + color: ${DarkGrey}; + font-weight: 600; + } + border-radius: 20px; +`; + +const Legend = styled.div` + display: flex; + gap: 20px; +`; + +const LegendItem = styled.div` + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 500; + color: ${DarkGrey}; + + & > span { + width: 15px; + height: 15px; + border-radius: 50%; + background-color: ${(props) => props.color}; + } +`; + +const PieChartStyle = styled.div` + display: inline-block; + position: relative; + width: 120px; + height: 120px; + background: ${(props) => `conic-gradient( + #fff1e1 0% ${props.piechart[0]}%, + #ff9a62 ${props.piechart[0]}% ${props.piechart[0] + props.piechart[1]}%, + ${Orange} ${props.piechart[0] + props.piechart[1]}% 100% + )`}; + border-radius: 50%; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); +`; diff --git a/src/components/storeDetail/PopOver.jsx b/src/components/storeDetail/PopOver.jsx new file mode 100644 index 0000000..185dcdf --- /dev/null +++ b/src/components/storeDetail/PopOver.jsx @@ -0,0 +1,58 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { Orange, Grey, White } from '../../color'; + +const PopOver = ({ text }) => { + const [isHovered, setIsHovered] = useState(); + + return ( + setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> + + {isHovered && ( + +

{text}

+
+ )} +
+ ); +}; + +export default PopOver; + +const PopOverContainer = styled.div` + display: flex; + align-items: center; + position: relative; +`; + +const Button = styled.div` + width: 15px; + height: 15px; + border-radius: 100px; + display: flex; + text-align: center; + align-items: center; + justify-content: center; + border: 1px solid ${Orange}; + color: ${Orange}; + font-size: 10px; + font-weight: 600; +`; + +const PopoverText = styled.div` + width: fit-content; + min-width: 400px; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + background-color: ${White}; + z-index: 10; + padding: 30px; + margin: 50px; + height: 70px; + box-shadow: 2px 2px 2px 2px ${Grey}; + border-radius: 20px; + + font-size: 13px; +`; diff --git a/src/components/storeDetail/ReviewDetail.jsx b/src/components/storeDetail/ReviewDetail.jsx new file mode 100644 index 0000000..60979ab --- /dev/null +++ b/src/components/storeDetail/ReviewDetail.jsx @@ -0,0 +1,70 @@ +import styled from 'styled-components'; +import { Grey, Orange } from '../../color'; +import PopOver from './PopOver'; +import { Keyword } from '../common'; +import PieChart from './PieChart'; + +const ReviewDetail = ({ store }) => { + if (!store || Object.keys(store).length === 0) { + return

로딩 중...

; + } + return ( + <> + + +

긍정/부정/중립 리뷰

+ +
+ +
+ + +

긍정 키워드

+
+ + +

부정 키워드

+
+ +
+ + +

추천 메뉴

+ +
+

{store.recommendMenu}

+
+ + ); +}; + +export default ReviewDetail; + +const ContentBox = styled.div` + width: 100%; + margin-top: 20px; + padding: 20px; + display: flex; + flex-direction: column; + //border-bottom: 1px solid ${Grey}; + //border-radius: 10px; + //box-shadow: 1px 1px 1px ${Grey}; + + gap: 10px; +`; + +const TitleBox = styled.div` + display: flex; + flex-direction: row; + gap: 10px; + + & > h3 { + color: ${Orange}; + } +`; diff --git a/src/components/storeDetail/StoreInfoCard.jsx b/src/components/storeDetail/StoreInfoCard.jsx new file mode 100644 index 0000000..8416477 --- /dev/null +++ b/src/components/storeDetail/StoreInfoCard.jsx @@ -0,0 +1,43 @@ +import styled from 'styled-components'; +import { DarkGrey } from '../../color'; + +const StoreInfoCard = ({ icon, text, iconColor }) => { + return ( + + {icon} +

{text}

+
+ ); +}; + +export default StoreInfoCard; + +const StoreInfoCardBox = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100px; + gap: 5px; + height: 100px; + + & > p { + font-weight: 600; + font-size: 14px; + font-weight: bold; + } +`; + +const IconBox = styled.div` + width: 25px; + height: 25px; + display: flex; + align-items: center; + justify-content: center; + + & > svg { + width: 100%; + height: 100%; + color: ${DarkGrey}; + } +`; diff --git a/src/components/storeDetail/StoreInsight.jsx b/src/components/storeDetail/StoreInsight.jsx new file mode 100644 index 0000000..e431af5 --- /dev/null +++ b/src/components/storeDetail/StoreInsight.jsx @@ -0,0 +1,82 @@ +import styled from 'styled-components'; +import PopOver from './PopOver'; +import StoreInfoCard from './StoreInfoCard'; +import { Orange, Grey, LightGrey } from '../../color'; +import { ReactComponent as Person } from '../../assets/Icon/detail/SoloDining.svg'; +import { ReactComponent as Parking } from '../../assets/Icon/detail/Parking.svg'; +import { ReactComponent as Dog } from '../../assets/Icon/detail/Dog.svg'; +import { ReactComponent as Clock } from '../../assets/Icon/detail/Clock.svg'; + +const StoreInsight = ({ store }) => { + const { isSoloDining, isParking, isPetFriendly, isWaiting } = store; + + const tip = { + isSoloDining: isSoloDining ? '가능' : '불가능', + isParking: isParking ? '가능' : '불가능', + isPetFriendly: isPetFriendly ? '가능' : '불가능', + isWaiting: isWaiting ? '있는' : '없는', + }; + + return ( + + +

AI리뷰 인사이트

+ +
+ + + } iconColor={Orange} text={`웨이팅 ${tip.isWaiting}`} /> + } iconColor={Orange} text={`혼밥 ${tip.isSoloDining}`} /> + } iconColor={Orange} text={`주차 ${tip.isParking}`} /> + } iconColor={Orange} text={`애견 동반 ${tip.isPetFriendly}`} /> + + + + +

한 줄 리뷰

+
+

{store.reviewSummary}

+
+
+ ); +}; + +export default StoreInsight; + +const StoreInsightContainer = styled.div` + display: flex; + justify-content: flex-start; + flex-direction: column; +`; + +const TitleBox = styled.div` + display: flex; + flex-direction: row; + gap: 10px; + & > h2 { + color: ${Orange}; + } +`; + +const ContentBox = styled.div` + width: 100%; + margin-top: 20px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 10px; + border-bottom: 1px solid ${Grey}; + //box-shadow: 1px 1px 1px ${Grey}; + //border-radius: 10px; + + & > p { + font-size: 15px; + } +`; + +const StoreInfoCardBox = styled.div` + max-width: 400px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); + gap: 10px; +`; diff --git a/src/components/storeDetail/StoreMap.jsx b/src/components/storeDetail/StoreMap.jsx new file mode 100644 index 0000000..5f98d9f --- /dev/null +++ b/src/components/storeDetail/StoreMap.jsx @@ -0,0 +1,62 @@ +import styled from 'styled-components'; +import { MyMap } from '../common'; +import { DarkGrey, Orange } from '../../color'; +import { LocationOn } from '@mui/icons-material'; +import { LocationButton } from '../common'; + +const StoreMap = ({ store }) => { + return ( + +
+ +

{store.address}

+
+ + + + +

{store.nearbyStation}

+ +
+
+ ); +}; + +export default StoreMap; + +const StoreMapContainer = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + + & > div { + display: flex; + flex-direction: row; + align-items: center; + gap: 5px; + + & > svg { + color: ${DarkGrey}; + } + } + padding-bottom: 30px; +`; + +const MapBox = styled.div` + border-radius: 20px; + width: 100%; + + height: 300px; + + & > div { + border-radius: 20px; + } +`; + +const StaitionBox = styled.div` + width: 100%; + display: flex; + flex-direction: row; + gap: 10px; +`; diff --git a/src/components/storeDetail/StoreOverview.jsx b/src/components/storeDetail/StoreOverview.jsx new file mode 100644 index 0000000..cbc9c2a --- /dev/null +++ b/src/components/storeDetail/StoreOverview.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import styled from 'styled-components'; +import DetailBox from './DetailBox'; +import { Orange, Grey } from '../../color'; +import { Phone, LocationOn, PunchClock } from '@mui/icons-material'; + +const StoreOverview = ({ store }) => { + return ( + + +
+ } label="전화" content={store.phone} /> + } label="위치" type="address" content={store.address} /> + } label="영업 시간" type="time" content={store.businessHours} /> + +
+
+
+ ); +}; + +export default StoreOverview; + +const StoreOverviewBox = styled.div` + & > h3 { + color: ${Orange}; + } +`; + +const ContentBox = styled.div` + width: 100%; + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; + box-shadow: 1px 1px 1px ${Grey}; + border: 1px solid ${Grey}; + border-radius: 10px; + + & > div { + display: flex; + flex-direction: column; + gap: 20px; + } +`; diff --git a/src/components/storeDetail/StorePreview.jsx b/src/components/storeDetail/StorePreview.jsx new file mode 100644 index 0000000..bb18b01 --- /dev/null +++ b/src/components/storeDetail/StorePreview.jsx @@ -0,0 +1,166 @@ +import styled from 'styled-components'; +import { DarkGrey, Orange } from '../../color'; +import { LocationButton } from '../common'; +import { ReactComponent as StarIcon } from '../../assets/Icon/detail/Star.svg'; + +const StorePreview = ({ store }) => { + const pathClickHandler = () => { + window.location.href = store.storeLink; + }; + + return ( + + + + 가게 이미지 + + + + +

{store.name}

+ +
+ +
+

{store.category}

+ + +

{store.rating}

+
+

리뷰 {store.reviewsCount}개

+
+
+

{store.nearbyStation}

+

+ {store.positiveRatio}% 긍정비율 +

+
+
+
+
+ ); +}; + +export default StorePreview; + +const StorePreviewContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + border-bottom: 1px solid ${Orange}; +`; + +const ImageContainer = styled.div` + width: 100%; + display: flex; +`; + +const ImageBox = styled.div` + width: 100%; + max-height: 400px; + border-radius: 10px; + overflow: hidden; + + & > img { + border-radius: 10px; + width: 100%; + max-width: 400px; + object-fit: cover; + } +`; + +const NameAndCategoryBox = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 10px; + + & > h1 { + font-weight: 500; + color: ${Orange}; + } +`; + +const ContentsBox = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + margin-top: 20px; + width: 80%; + padding-bottom: 10px; + gap: 10px; + font-weight: 500; +`; + +const RatingBox = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 20px; + + & > div > svg { + width: 20px; + height: 20px; + color: ${Orange}; + } + + & > div { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + & > div > p { + font-size: 18px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + } + + & > p { + text-align: left; + font-size: 16px; + color: ${DarkGrey}; + } + + & > p > span { + font-size: 18px; + font-weight: 700; + color: ${Orange}; + } + + @media screen and (max-width: 1024px) { + flex-direction: column; + & > div { + display: flex; + height: 30px; + gap: 10px; + flex-direction: row; + align-items: center; + text-align: center; + + & > div > p { + font-size: 18px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + } + } +`; + +const StarBox = styled.div` + display: flex; + flex-direction: row; + font-weight: 600; + justify-content: center; + align-items: center; + + & > svg { + width: 16px; + height: 16px; + } +`; diff --git a/src/components/storeDetail/StoreTip.jsx b/src/components/storeDetail/StoreTip.jsx new file mode 100644 index 0000000..edeb9df --- /dev/null +++ b/src/components/storeDetail/StoreTip.jsx @@ -0,0 +1,61 @@ +import styled from 'styled-components'; +import { Orange, Grey } from '../../color'; +import { ReactComponent as ParkingTip } from '../../assets/Icon/detail/PakingTip.svg'; +import { ReactComponent as WaitingTip } from '../../assets/Icon/detail/WaitingTip.svg'; + +const StoreTip = ({ store }) => { + return ( + +

AI TIP

+
+ + +

주차 꿀팁

+

{store.parkingTip}

+
+ + +

웨이팅 꿀팁

+

{store.waitingTip}

+
+
+
+ ); +}; + +export default StoreTip; + +const TipContainer = styled.div` + display: flex; + padding: 20px; + flex-direction: column; + gap: 20px; + width: 100%; + + & > h3 { + color: ${Orange}; + } + & > div { + display: flex; + flex-direction: row; + gap: 30px; + } + @media screen and (max-width: 1024px) { + & > div > div { + min-width: 100px; + } + } +`; + +const TipBox = styled.div` + width: 100%; + height: 200px; + display: flex; + gap: 10px; + border-radius: 20px; + border: 1px solid ${Orange}; + flex-direction: column; + justify-content: center; + padding: 20px; + box-shadow: 2px 2px 2px ${Grey}; +`; diff --git a/src/components/storeDetail/index.js b/src/components/storeDetail/index.js new file mode 100644 index 0000000..7fec556 --- /dev/null +++ b/src/components/storeDetail/index.js @@ -0,0 +1,7 @@ +import StorePreview from './StorePreview'; +import StoreMap from './StoreMap'; +import StoreInsight from './StoreInsight'; +import StoreOverview from './StoreOverview'; +import ReviewDetail from './ReviewDetail'; +import StoreTip from './StoreTip'; +export { StorePreview, StoreMap, StoreInsight, StoreOverview, ReviewDetail, StoreTip }; diff --git a/src/pages/DetailPage.jsx b/src/pages/DetailPage.jsx new file mode 100644 index 0000000..4201462 --- /dev/null +++ b/src/pages/DetailPage.jsx @@ -0,0 +1,160 @@ +import styled from 'styled-components'; +import { useStoreDetail } from '../store'; +import { useState, useEffect } from 'react'; +import { StorePreview, StoreMap, StoreInsight, StoreOverview, ReviewDetail, StoreTip } from '../components/storeDetail'; +import { Grey, White } from '../color'; +import { Footer } from '../components/common'; +import { useNavigate, useParams } from 'react-router-dom'; +import { getStoreDetail } from '../apis/api/getStoreDetail'; +import { Button } from '../components/common'; +import { ReactComponent as BookmarkIcon } from '../assets/Icon/detail/Bookmark.svg'; + +const DetailPage = () => { + const [item, setItem] = useState({}); + const { id } = useParams(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const storeId = id; + const { setStoreDetail } = useStoreDetail(); + + const fetchStoreDetail = async (storeId) => { + setIsLoading(true); + try { + const response = await getStoreDetail({ storeId }); + const newData = response.data; + if (typeof newData.businessHours === 'string') { + try { + const jsonString = newData.businessHours.replace(/'/g, '"'); + newData.businessHours = JSON.parse(jsonString); + } catch (e) { + console.error('Failed to parse business_hours:', e); + } + } + setItem(newData); + console.log(newData); + setStoreDetail(newData); + } catch (error) { + console.log(error); + } + setIsLoading(false); + }; + + useEffect(() => { + if (storeId) { + fetchStoreDetail(storeId); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [storeId]); + + const buttonClickHandler = () => { + navigate('/webmap'); + }; + + const { toggleStoreDetailPage, isStoreDetailPage } = useStoreDetail(); + + if (!isStoreDetailPage) { + toggleStoreDetailPage(); + } + + return ( + <> + {!isLoading && ( + + + + + + + + )} + {isLoading &&

} +