From 658d45549f762f1c94f8c7de149571f6730b7f7a Mon Sep 17 00:00:00 2001 From: devhaeun Date: Fri, 7 Feb 2025 12:25:09 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Design:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EC=88=98=EC=A0=95=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 래플 업로드 버튼 디자인 수정 '관심' -> '찜한래플' 이름 수정 --- src/assets/header/icon-upload.svg | 8 ++------ src/components/ContainerLarge.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/assets/header/icon-upload.svg b/src/assets/header/icon-upload.svg index 3034f41..70d89da 100644 --- a/src/assets/header/icon-upload.svg +++ b/src/assets/header/icon-upload.svg @@ -1,7 +1,3 @@ - - - - - - + + \ No newline at end of file diff --git a/src/components/ContainerLarge.tsx b/src/components/ContainerLarge.tsx index c01b1a1..0959483 100644 --- a/src/components/ContainerLarge.tsx +++ b/src/components/ContainerLarge.tsx @@ -143,7 +143,7 @@ const ContainerLarge = ({isLoggedIn}:{isLoggedIn:boolean}) => { - 관심 + 찜한래플 @@ -153,10 +153,10 @@ const ContainerLarge = ({isLoggedIn}:{isLoggedIn:boolean}) => { 충전/환전 - navigate('/raffle-upload')}> - - 래플 업로드 - + navigate('/raffle-upload')}> + + 래플 업로드 + ); From efb9beafa473dc3051d9db9e8747f0a9b2d91b18 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Fri, 7 Feb 2025 12:52:37 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Design:=20=ED=97=A4=EB=8D=94=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EA=B0=84=EA=B2=A9=20=EB=B0=8F=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ResponsiveHeader.tsx | 445 ++++++++++++++++++++++++++++ src/layout/RootLayout.tsx | 3 +- 2 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 src/components/ResponsiveHeader.tsx diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx new file mode 100644 index 0000000..7f56ede --- /dev/null +++ b/src/components/ResponsiveHeader.tsx @@ -0,0 +1,445 @@ +import styled from 'styled-components'; +import media from '../styles/media'; +import icLogo from '../assets/header/icon-logo.svg'; +// import imgLogo from '../assets/logo.png'; +import icHamburger from '../assets/header/icon-hamburger.svg'; +import ticket from '../assets/ticketLogo.png'; +import { ReactComponent as IcNotice } from '../assets/header/icon-notice.svg'; +import icSetting from '../assets/header/icon-setting.svg'; +import icSearch from '../assets/header/icon-search.svg'; +import icHeart from '../assets/header/icon-heart.svg'; +import icMyPage from '../assets/header/icon-mypage.svg'; +import icUpload from '../assets/header/icon-upload.svg'; +import imgTicket from '../assets/ticket.svg'; +import { useNavigate } from "react-router-dom"; +import { ChangeEvent, useEffect, useRef, useState } from "react"; +import CategoryMenu from './CategoryMenu'; +import { useModalContext } from './Modal/context/ModalContext'; +import SplashModal from '../pages/login/components/SplashModal'; +import imgVector from '../assets/Vector.png'; +import { ReactComponent as IcList } from '../assets/icList.svg'; +import icDel from '../assets/icDel.svg'; + +const recentKeywords = ['애플워치','애플워치','애플워치','애플워치', + '애플워치','애플워치','애플워치','애플워치','애플워치','애플워치', +]; + +const ResponsiveHeader = () => { + const navigate = useNavigate(); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isCatClicked, setIsCatClicked] = useState(false); + const { openModal } = useModalContext(); + const [isSearchClicked, setIsSearchClicked] = useState(false); + const searchRef = useRef(null); + const [searchText, setSearchText] = useState(''); + const categoryRef = useRef(null); + + const handleCategoryOut = (e:MouseEvent) => { + const currentCategoryRef = categoryRef.current; + if (currentCategoryRef && !currentCategoryRef.contains(e.target as Node)) { + setIsCatClicked(false); + console.log("handleCategoryOut!"); + } + }; + + const handleClickOutside = (e:MouseEvent) => { + const currentSearchRef = searchRef.current; + if (currentSearchRef && !currentSearchRef.contains(e.target as Node)) { + setIsSearchClicked(false); + } + }; + + const handleSearchInput = (e: ChangeEvent) => { + setSearchText(e.target.value); + }; + + const handleOpenModal = () => { + openModal(({ onClose }) => ); + }; + const onClickLoginBtn = () => { + if (!isLoggedIn) handleOpenModal(); + } + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('mousedown', handleCategoryOut); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("mousedown", handleCategoryOut); + } + }, [searchRef.current, categoryRef.current]); + + return ( + + + + {isLoggedIn ? '로그아웃' : '로그인'} + + + + + 알림 + + + + + 설정 + + + + navigate('/')} /> + + { + isCatClicked + ? setIsCatClicked(false) + : setIsCatClicked(true)}}> + + 카테고리 + + {isCatClicked && + } + + + + setIsSearchClicked(true)} + value={searchText} + onChange={handleSearchInput} + /> + + + + + + 최근 검색 + + + {recentKeywords.map((v,_) => ( + + {v} + + + ))} + + + + + + 현재 인기있는 검색어 + + + {recentKeywords.map((v,_) => ( + + + {v} + + ))} + + + + + + + 찜한래플 + + + + 마이페이지 + + navigate('/change')}> + + 충전/환전 + + navigate('/raffle-upload')}> + + 래플 업로드 + + + + ); +}; + +export default ResponsiveHeader; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + width: 1084px; + height: 188px; + box-sizing: border-box; + z-index: 100; + ${media.medium` + // display: none; + `} +`; + +const TopContainer = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + margin: 0 34px 26px 0; +`; + +const LoginBtn = styled.button<{ state: string }>` + width: 99.355px; + height: 26.644px; + border-radius: 40px; + ${(props) => + props.state === 'true' + ? `border: 1px solid #8F8E94; + background-color: transparent; + color: #8F8E94;` + : `border: 1px solid #C908FF; + background: rgba(201, 8, 255, 0.20); + color: #C908FF;`} + font-family: Pretendard; + font-size: 15px; + font-weight: 500; + line-height: 18px; + letter-spacing: -0.165px; + cursor: pointer; +`; + +const IconTextDiv = styled.div<{ + fontSize: string; + color?: string; + fontWeight?: string; +}>` + font-size: ${(props) => props.fontSize}; + color: ${(props) => props.color || '#8F8E94'}; + text-align: center; + font-family: Pretendard; + font-style: normal; + font-weight: ${(props) => props.fontWeight || '600'}; + line-height: 18px; + letter-spacing: -0.165px; +`; + +const SearchBoxContainer = styled.div` + display: flex; + justify-content: space-between; + // justify-content: center; + align-items: center; + box-sizing: border-box; +`; + +const LogoImg = styled.img` + width: 133px; + height: 64px; + margin-right: 15px; + margin-bottom: 5px; + flex-shrink: 0; + max-width: 100%; + object-fit: contain; + &:hover { + cursor: pointer; + }; +` + +const CategoryContainer = styled.div` + position: relative; +`; + +const SearchBoxDiv = styled.div` + position: relative; + width: 590px; + // flex: 1; + // max-width: 590px; + // min-width: 560px; + height: 42px; + border-radius: 51px; + border: 1.5px solid #C908FF; + box-sizing: border-box; + padding: 3px 20px; + display: flex; +` + +const Img = styled.img` + margin: 3px 0; +`; + +const TicketImg = styled.img` + position: absolute; + bottom: 105%; + left: 50%; + transform: translateX(-50%); +`; + +const SearchInput = styled.input` + width: 100%; + height: 100%; + border: none; + outline: none; + font-size: 18px; + font-family: Pretendard; + font-style: normal; + line-height: 18px; + letter-spacing: -0.165px; +`; + +const SearchIcon = styled.img` + cursor: pointer; + width: 20.975px; +`; + +const KeywordContainer = styled.div<{$show:string}>` + // width: 560px; + width: 100%; + height: 386px; + border-radius: 18px; + border: 1px solid #E4E4E4; + background-color: #FFF; + position: absolute; + left: 0; + top: 120%; + padding: 38px 43px 5px 43px; + box-sizing: border-box; + display: ${props => props.$show==='true' + ? 'box' + : 'none' + }; +` +const KeywordBox = styled.div` + box-sizing: border-box; + min-height: 129px; + padding-bottom: 37px; +` +const KeywordTitle = styled.div` + display: flex; + align-items: baseline; +` +const Span = styled.span` + padding-left: 18px; + padding-bottom: 19px; + color: #000; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 128.571% */ + letter-spacing: -0.165px; +` +const RecentKeywordsBox = styled.div` + display: flex; + flex-wrap: wrap; + row-gap: 17px; + column-gap: 16px; +` +const RecentKeyword = styled.div` + width: 81px; + height: 20px; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + gap: 9px; + padding: 3px 7px; + border-radius: 12px; + background: #E4E4E4; + box-sizing: border-box; + + color: #000; + text-align: center; + font-family: Pretendard; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 36.832px; /* 306.932% */ + + &:hover { + cursor: default; + } +` +const DelImg = styled.img` + &:hover { + cursor: pointer; + } +` +const HotKeywordsBox = styled.div` + height: 148px; + display: flex; + flex-direction: column; + flex-wrap: wrap; + gap: 17px; +` +const HotKeyword = styled.div` + width: 228px; + display: flex; + align-items: center; + gap: 17px; + + color: #000; + font-family: Pretendard; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; +` + +const SmallIconDiv = styled.div` + display: flex; + column-gap: 11px; + align-items: center; + justify-content: space-evenly; + height: 65px; + cursor: pointer; +`; + +const IconDiv = styled.div` + display: flex; + flex-direction: column; + row-gap: 6px; + align-items: center; + justify-content: space-evenly; + height: 45px; + min-width: 61px; + // margin: 0 25px 0 0; + cursor: pointer; +`; + +const IconHamburgerDiv = styled.div` + display: flex; + flex-direction: column; + row-gap: 6px; + align-items: center; + justify-content: space-between; + height: 46px; + min-width: 61px; + cursor: pointer; +`; + +const UploadBtn = styled.button` + color: white; + text-align: center; + font-family: Pretendard; + font-size: 8px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 225% */ + letter-spacing: -0.165px; + box-sizing: border-box; + width: 56px; + height: 56px; + flex-shrink: 0; + background-color: #c908ff; + border-radius: 9px; + border: 1px solid #c908ff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + padding-top: 9px 0 3px 0; + margin-bottom: 8px; + cursor: pointer; +`; + +const LineDiv = styled.div<{ height: string; margin: string }>` + width: 1px; + height: ${(props) => props.height}; + background: #8f8e94; + margin: ${(props) => props.margin}; +`; diff --git a/src/layout/RootLayout.tsx b/src/layout/RootLayout.tsx index 16e10da..f73da6a 100644 --- a/src/layout/RootLayout.tsx +++ b/src/layout/RootLayout.tsx @@ -2,11 +2,12 @@ import styled from 'styled-components'; import Header from '../components/Header'; import { Outlet } from 'react-router-dom'; import HeaderIconMenu from '../components/HeaderIconMenu'; +import ResponsiveHeader from '../components/ResponsiveHeader'; const RootLayout = () => { return ( -
+ {/* */} From 7be94b9f87bf699259bc4533229356145c3c4461 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Fri, 7 Feb 2025 13:16:29 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Design:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=ED=83=9C=EB=B8=94=EB=A6=BF=20=EB=B7=B0=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ResponsiveHeader.tsx | 40 +++++++++++++++++++--- src/pages/homepage/components/AdBanner.tsx | 6 +++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index 7f56ede..89e4ec0 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -70,12 +70,13 @@ const ResponsiveHeader = () => { }, [searchRef.current, categoryRef.current]); return ( + <> {isLoggedIn ? '로그아웃' : '로그인'} - + 알림 @@ -100,7 +101,7 @@ const ResponsiveHeader = () => { } - + setIsSearchClicked(true)} @@ -160,6 +161,8 @@ const ResponsiveHeader = () => { + + ); }; @@ -168,13 +171,14 @@ export default ResponsiveHeader; const Wrapper = styled.div` display: flex; flex-direction: column; - width: 1084px; + max-width: 1084px; height: 188px; box-sizing: border-box; z-index: 100; ${media.medium` - // display: none; - `} + width: 650px; + // padding: 0 40px; + `} `; const TopContainer = styled.div` @@ -182,6 +186,9 @@ const TopContainer = styled.div` justify-content: flex-end; align-items: center; margin: 0 34px 26px 0; + ${media.medium` + margin-right: 10px; + `} `; const LoginBtn = styled.button<{ state: string }>` @@ -202,6 +209,9 @@ const LoginBtn = styled.button<{ state: string }>` line-height: 18px; letter-spacing: -0.165px; cursor: pointer; + ${media.medium` + display: none; + `} `; const IconTextDiv = styled.div<{ @@ -238,6 +248,10 @@ const LogoImg = styled.img` &:hover { cursor: pointer; }; + ${media.medium` + width: 119px; + height: 56px; + `} ` const CategoryContainer = styled.div` @@ -263,10 +277,14 @@ const Img = styled.img` `; const TicketImg = styled.img` + width: 88px; position: absolute; bottom: 105%; left: 50%; transform: translateX(-50%); + ${media.medium` + width: 79px; + `} `; const SearchInput = styled.input` @@ -442,4 +460,16 @@ const LineDiv = styled.div<{ height: string; margin: string }>` height: ${(props) => props.height}; background: #8f8e94; margin: ${(props) => props.margin}; + + &.line-1 { + ${media.medium` + display: none; + `} + } `; + +const Line = styled.div` + width: 100%; + height: 1px; + background: #E4E4E4; +` \ No newline at end of file diff --git a/src/pages/homepage/components/AdBanner.tsx b/src/pages/homepage/components/AdBanner.tsx index 3f009b2..d866970 100644 --- a/src/pages/homepage/components/AdBanner.tsx +++ b/src/pages/homepage/components/AdBanner.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import Slider from 'react-slick'; import 'slick-carousel/slick/slick.css'; import 'slick-carousel/slick/slick-theme.css'; +import media from '../../../styles/media'; function AdBanner() { const settings = { @@ -68,7 +69,10 @@ const Wrapper = styled.div` color: rgba(201, 8, 255, 0.2); /* 선택되지 않은 점의 색상 */ font-size: 8px; } - } + }; + ${media.medium` + margin-top: 26px; + `} `; const AdBox = styled.a` From 00bcceccb8110623834af527ca69b7173f2362a8 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Fri, 7 Feb 2025 16:53:52 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Design:=20=EA=B2=80=EC=83=89=EC=B0=BD=20?= =?UTF-8?q?=EC=9C=84=20=ED=8B=B0=EC=BC=93=20=EC=9C=84=EC=B9=98=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 검색창 위 티켓 위치 조정, 세미콜론 추가 --- src/components/ResponsiveHeader.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index 89e4ec0..ada3c67 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -1,7 +1,6 @@ import styled from 'styled-components'; import media from '../styles/media'; import icLogo from '../assets/header/icon-logo.svg'; -// import imgLogo from '../assets/logo.png'; import icHamburger from '../assets/header/icon-hamburger.svg'; import ticket from '../assets/ticketLogo.png'; import { ReactComponent as IcNotice } from '../assets/header/icon-notice.svg'; @@ -58,7 +57,7 @@ const ResponsiveHeader = () => { }; const onClickLoginBtn = () => { if (!isLoggedIn) handleOpenModal(); - } + }; useEffect(() => { document.addEventListener('mousedown', handleClickOutside); @@ -279,7 +278,7 @@ const Img = styled.img` const TicketImg = styled.img` width: 88px; position: absolute; - bottom: 105%; + bottom: 102%; left: 50%; transform: translateX(-50%); ${media.medium` From 7d6b440acc4a07398cd96b26eee396bc6ed3d284 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Fri, 7 Feb 2025 18:06:58 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Feat:=20=ED=97=A4=EB=8D=94=20=ED=98=B8?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 헤더에 아이콘 호버 이벤트 시 스타일 변화 추가 --- src/assets/header/icon-setting.svg | 6 ++--- src/components/ResponsiveHeader.tsx | 38 +++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/assets/header/icon-setting.svg b/src/assets/header/icon-setting.svg index c4da854..f0ee8f5 100644 --- a/src/assets/header/icon-setting.svg +++ b/src/assets/header/icon-setting.svg @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index ada3c67..e379d20 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -4,7 +4,8 @@ import icLogo from '../assets/header/icon-logo.svg'; import icHamburger from '../assets/header/icon-hamburger.svg'; import ticket from '../assets/ticketLogo.png'; import { ReactComponent as IcNotice } from '../assets/header/icon-notice.svg'; -import icSetting from '../assets/header/icon-setting.svg'; +import { ReactComponent as IcSetting } from '../assets/header/icon-setting.svg'; +// import icSetting from '../assets/header/icon-setting.svg'; import icSearch from '../assets/header/icon-search.svg'; import icHeart from '../assets/header/icon-heart.svg'; import icMyPage from '../assets/header/icon-mypage.svg'; @@ -77,12 +78,12 @@ const ResponsiveHeader = () => { - + 알림 - + 설정 @@ -198,10 +199,21 @@ const LoginBtn = styled.button<{ state: string }>` props.state === 'true' ? `border: 1px solid #8F8E94; background-color: transparent; - color: #8F8E94;` + color: #8F8E94; + &:hover { + border: 1px solid #000; + color: #000; + } + ` : `border: 1px solid #C908FF; background: rgba(201, 8, 255, 0.20); - color: #C908FF;`} + color: #C908FF; + &:hover { + border: 1px solid #C908FF; + background: #C908FF; + color: #FFF; + }; + `} font-family: Pretendard; font-size: 15px; font-weight: 500; @@ -395,6 +407,10 @@ const HotKeyword = styled.div` font-style: normal; font-weight: 400; line-height: normal; + + &:hover { + cursor: pointer; + }; ` const SmallIconDiv = styled.div` @@ -404,6 +420,12 @@ const SmallIconDiv = styled.div` justify-content: space-evenly; height: 65px; cursor: pointer; + &:hover > ${IconTextDiv} { + color: #040404; + }; + &:hover .svg { + fill: #040404; + } `; const IconDiv = styled.div` @@ -416,6 +438,9 @@ const IconDiv = styled.div` min-width: 61px; // margin: 0 25px 0 0; cursor: pointer; + &:hover > ${IconTextDiv} { + color: #C908FF; + } `; const IconHamburgerDiv = styled.div` @@ -427,6 +452,9 @@ const IconHamburgerDiv = styled.div` height: 46px; min-width: 61px; cursor: pointer; + &:hover > ${IconTextDiv} { + color: #C908FF; + } `; const UploadBtn = styled.button` From d15314b238b6d19a6d8c669e88ab84f5900e0d10 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Sun, 9 Feb 2025 00:09:58 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=EC=96=B4=20API?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20=EC=A4=91=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인기 검색어 데이터 API 받아와서 보여주기 구현 --- src/apis/axiosInstance.ts | 7 ++++ src/components/ResponsiveHeader.tsx | 63 ++++++++++++++++++++++------- src/types/searchKeywords.ts | 9 +++++ 3 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 src/apis/axiosInstance.ts create mode 100644 src/types/searchKeywords.ts diff --git a/src/apis/axiosInstance.ts b/src/apis/axiosInstance.ts new file mode 100644 index 0000000..4a2fa23 --- /dev/null +++ b/src/apis/axiosInstance.ts @@ -0,0 +1,7 @@ +import axios from "axios"; + +const axiosInstance = axios.create({ + baseURL: `${import.meta.env.VITE_API_BASE_URL}`, +}); + +export default axiosInstance; \ No newline at end of file diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index e379d20..1d4c4ad 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -19,10 +19,8 @@ import SplashModal from '../pages/login/components/SplashModal'; import imgVector from '../assets/Vector.png'; import { ReactComponent as IcList } from '../assets/icList.svg'; import icDel from '../assets/icDel.svg'; - -const recentKeywords = ['애플워치','애플워치','애플워치','애플워치', - '애플워치','애플워치','애플워치','애플워치','애플워치','애플워치', -]; +import axiosInstance from '../apis/axiosInstance'; +import { TSearch } from '../types/searchKeywords'; const ResponsiveHeader = () => { const navigate = useNavigate(); @@ -33,6 +31,19 @@ const ResponsiveHeader = () => { const searchRef = useRef(null); const [searchText, setSearchText] = useState(''); const categoryRef = useRef(null); + const [hotKeywords, setHotKeywords] = useState([]); + const [recentKeywords, setRecentKeywords] = useState([]); + + const getSearch = async () => { + const { data }:{data:TSearch} = await axiosInstance.get( + isLoggedIn ? '/api/member/search/raffles' + : '/api/permit/search' + ); + + console.log('getSearch:', data); + setHotKeywords(data.result.popularSearch); + setRecentKeywords(data.result.recentSearch); + }; const handleCategoryOut = (e:MouseEvent) => { const currentCategoryRef = categoryRef.current; @@ -60,6 +71,15 @@ const ResponsiveHeader = () => { if (!isLoggedIn) handleOpenModal(); }; + const onClickSearchInput = () => { + setIsSearchClicked(true); + }; + + // 시작하자마자 호출될 API + useEffect(() => { + getSearch(); + }, []); + useEffect(() => { document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleCategoryOut); @@ -104,7 +124,7 @@ const ResponsiveHeader = () => { setIsSearchClicked(true)} + onClick={()=>onClickSearchInput()} value={searchText} onChange={handleSearchInput} /> @@ -113,27 +133,33 @@ const ResponsiveHeader = () => { ref={searchRef} $show={String(isSearchClicked)} > - + {isLoggedIn + ? 최근 검색 - {recentKeywords.map((v,_) => ( + {recentKeywords.length!==0 ? + recentKeywords.map((v,_) => ( {v} - ))} + )) + : 최근 검색 내역이 없습니다. + } + : <> + } 현재 인기있는 검색어 - {recentKeywords.map((v,_) => ( + {hotKeywords.map((v,_) => ( {v} @@ -171,7 +197,7 @@ export default ResponsiveHeader; const Wrapper = styled.div` display: flex; flex-direction: column; - max-width: 1084px; + width: 1084px; height: 188px; box-sizing: border-box; z-index: 100; @@ -316,15 +342,16 @@ const SearchIcon = styled.img` `; const KeywordContainer = styled.div<{$show:string}>` - // width: 560px; - width: 100%; - height: 386px; + width: 560px; + // width: 100%; + // height: 386px; border-radius: 18px; border: 1px solid #E4E4E4; background-color: #FFF; position: absolute; - left: 0; + left: 50%; top: 120%; + transform: translateX(-50%); padding: 38px 43px 5px 43px; box-sizing: border-box; display: ${props => props.$show==='true' @@ -383,6 +410,14 @@ const RecentKeyword = styled.div` cursor: default; } ` +const KeywordSpan = styled.span` + color: #8F8E94; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 36.832px; /* 306.932% */ +` const DelImg = styled.img` &:hover { cursor: pointer; diff --git a/src/types/searchKeywords.ts b/src/types/searchKeywords.ts new file mode 100644 index 0000000..aeb3f30 --- /dev/null +++ b/src/types/searchKeywords.ts @@ -0,0 +1,9 @@ +export type TSearch = { + isSuccess: boolean, + code: string, + message: string, + result: { + recentSearch: string[], + popularSearch: string[] + } +}; \ No newline at end of file From ee46375c0b1d9fca18fa64f89a94ec44c8f231d7 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Sun, 9 Feb 2025 00:37:00 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Feat:=20=EC=B5=9C=EA=B7=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 임시 데이터 이용해서 최근 검색어 삭제 화면 구현 DELETE 연결 X --- src/components/ResponsiveHeader.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index 1d4c4ad..a0218a3 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -33,6 +33,9 @@ const ResponsiveHeader = () => { const categoryRef = useRef(null); const [hotKeywords, setHotKeywords] = useState([]); const [recentKeywords, setRecentKeywords] = useState([]); + const [dummy, setDummy] = useState([ + '애플워치', '아이폰', '에어팟', '안경', '코트' + ]); const getSearch = async () => { const { data }:{data:TSearch} = await axiosInstance.get( @@ -44,6 +47,7 @@ const ResponsiveHeader = () => { setHotKeywords(data.result.popularSearch); setRecentKeywords(data.result.recentSearch); }; + const delSearch = async () => await axiosInstance.delete('/api/member/search'); const handleCategoryOut = (e:MouseEvent) => { const currentCategoryRef = categoryRef.current; @@ -64,6 +68,12 @@ const ResponsiveHeader = () => { setSearchText(e.target.value); }; + const handleDelKeyword = (keyword:string) => { + // delSearch(): 해당 키워드 서버에서 삭제 + setDummy(prev => prev.filter(v => v!==keyword)); + console.log(dummy); + } + const handleOpenModal = () => { openModal(({ onClose }) => ); }; @@ -133,18 +143,20 @@ const ResponsiveHeader = () => { ref={searchRef} $show={String(isSearchClicked)} > - {isLoggedIn + {isLoggedIn===false ? 최근 검색 - {recentKeywords.length!==0 ? - recentKeywords.map((v,_) => ( + {dummy.length!==0 ? + dummy.map((v,_) => ( {v} - + handleDelKeyword(v)} + /> )) : 최근 검색 내역이 없습니다. From f30053fc9ef114cf13e9961790af870c33b5d13e Mon Sep 17 00:00:00 2001 From: devhaeun Date: Mon, 10 Feb 2025 23:43:21 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Feat:=20=ED=97=A4=EB=8D=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isAuthenticated 여부에 따른 로그인 버튼 구현 검색 시 검색 결과 페이지 이동 구현 최근 검색어 POST 오류: 500 에러 --- src/components/ResponsiveHeader.tsx | 40 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index f2182f1..31b9fbb 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -12,7 +12,7 @@ import icMyPage from '../assets/header/icon-mypage.svg'; import icUpload from '../assets/header/icon-upload.svg'; import imgTicket from '../assets/ticket.svg'; import { useNavigate } from "react-router-dom"; -import { ChangeEvent, useEffect, useRef, useState } from "react"; +import { ChangeEvent, KeyboardEvent, useContext, useEffect, useRef, useState } from "react"; import CategoryMenu from './CategoryMenu'; import { useModalContext } from './Modal/context/ModalContext'; import SplashModal from '../pages/login/components/SplashModal'; @@ -21,6 +21,7 @@ import { ReactComponent as IcList } from '../assets/icList.svg'; import icDel from '../assets/icDel.svg'; import axiosInstance from '../apis/axiosInstance'; import { TSearch } from '../types/searchKeywords'; +import { AuthContext, useAuth } from '../context/AuthContext'; const ResponsiveHeader = () => { const navigate = useNavigate(); @@ -36,14 +37,15 @@ const ResponsiveHeader = () => { const [dummy, setDummy] = useState([ '애플워치', '아이폰', '에어팟', '안경', '코트' ]); + const { isAuthenticated, logout } = useAuth(); const getSearch = async () => { const { data }:{data:TSearch} = await axiosInstance.get( - isLoggedIn ? '/api/member/search/raffles' + isAuthenticated ? '/api/member/search' : '/api/permit/search' ); - console.log('getSearch:', data); + // console.log('getSearch:', data); setHotKeywords(data.result.popularSearch); setRecentKeywords(data.result.recentSearch); }; @@ -67,6 +69,20 @@ const ResponsiveHeader = () => { const handleSearchInput = (e: ChangeEvent) => { setSearchText(e.target.value); }; + const handleSearchEnter = (e:KeyboardEvent) => { + if (e.key === "Enter") { + if (isAuthenticated) { + axiosInstance.post('/api/member/search', searchText); + // formData.append('recentSearch', searchText); + // fetch(`/api/member/search`, { + // method: "POST", + // body: formData, + // }); + } + navigate(`/search/${searchText}`); + setIsSearchClicked(false); + } + } const handleDelKeyword = (keyword:string) => { // delSearch(): 해당 키워드 서버에서 삭제 @@ -78,13 +94,10 @@ const ResponsiveHeader = () => { openModal(({ onClose }) => ); }; const onClickLoginBtn = () => { - if (!isLoggedIn) handleOpenModal(); + if (!isAuthenticated) handleOpenModal(); + else logout(); }; - // const onClickSearchInput = () => { - // setIsSearchClicked(true); - // }; - // 시작하자마자 호출될 API useEffect(() => { getSearch(); @@ -103,8 +116,8 @@ const ResponsiveHeader = () => { <> - - {isLoggedIn ? '로그아웃' : '로그인'} + + {isAuthenticated ? '로그아웃' : '로그인'} @@ -133,10 +146,11 @@ const ResponsiveHeader = () => { setIsSearchClicked(true)} value={searchText} onChange={handleSearchInput} + onKeyUp={handleSearchEnter} /> { 최근 검색 - {dummy.length!==0 ? - dummy.map((v,_) => ( + {recentKeywords.length!==0 ? + recentKeywords.map((v,_) => ( {v} Date: Mon, 10 Feb 2025 23:47:00 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Chore:=20AdBanner=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/homepage/components/AdBanner.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/homepage/components/AdBanner.tsx b/src/pages/homepage/components/AdBanner.tsx index 0969804..7cc4474 100644 --- a/src/pages/homepage/components/AdBanner.tsx +++ b/src/pages/homepage/components/AdBanner.tsx @@ -69,11 +69,7 @@ const Wrapper = styled.div` color: rgba(201, 8, 255, 0.2); /* 선택되지 않은 점의 색상 */ font-size: 8px; } -<<<<<<< HEAD - }; -======= } ->>>>>>> 557e220385c92aee84baf142efb02e03c7f625bc ${media.medium` margin-top: 26px; `} From 5a2e6e7fc4eed6b3a4cb6e8d6c3e395e257c61a7 Mon Sep 17 00:00:00 2001 From: devhaeun Date: Tue, 11 Feb 2025 18:36:56 +0900 Subject: [PATCH 10/10] =?UTF-8?q?Feat:=20=EC=B5=9C=EA=B7=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EA=B8=B0=EB=8A=A5=20=EC=99=84=EC=84=B1=20?= =?UTF-8?q?#55?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 검색 시 최근 검색어 바로 반영: zustand 이용 - 검색어 삭제 시 최근 검색어 바로 삭제 - 최근 검색어 받아오기 --- package-lock.json | 10 ------ package.json | 4 +-- src/components/ResponsiveHeader.tsx | 38 ++++++++++------------- src/pages/homepage/homePage.tsx | 2 +- src/pages/raffleList/SearchResultPage.tsx | 15 +++++++-- src/store/store.ts | 11 +++++++ 6 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 src/store/store.ts diff --git a/package-lock.json b/package-lock.json index b582139..a4f4cb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "pretendard": "^1.3.9", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", - "react-daum-postcode": "^3.2.0", "react-datepicker": "^8.0.0", "react-daum-postcode": "^3.2.0", "react-dom": "^18.3.1", @@ -6799,14 +6798,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/react-daum-postcode": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-daum-postcode/-/react-daum-postcode-3.2.0.tgz", - "integrity": "sha512-NHY8TUicZXMqykbKYT8kUo2PEU7xu1DFsdRmyWJrLEUY93Xhd3rEdoJ7vFqrvs+Grl9wIm9Byxh3bI+eZxepMQ==", - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/react-datepicker": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.0.0.tgz", @@ -8123,7 +8114,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", - "license": "MIT", "engines": { "node": ">=12.20.0" }, diff --git a/package.json b/package.json index e363d50..72b1348 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,16 @@ "@iconify/react": "^5.2.0", "@mui/icons-material": "^6.4.1", "@mui/material": "^6.4.1", - "@types/react-slick": "^0.23.13", "@tanstack/react-query": "^5.66.0", + "@types/react-slick": "^0.23.13", "axios": "^1.7.9", "chart.js": "^4.4.7", "lucide-react": "^0.474.0", "pretendard": "^1.3.9", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", - "react-daum-postcode": "^3.2.0", "react-datepicker": "^8.0.0", + "react-daum-postcode": "^3.2.0", "react-dom": "^18.3.1", "react-router-dom": "^7.1.1", "react-slick": "^0.30.3", diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx index 31b9fbb..c826435 100644 --- a/src/components/ResponsiveHeader.tsx +++ b/src/components/ResponsiveHeader.tsx @@ -12,7 +12,7 @@ import icMyPage from '../assets/header/icon-mypage.svg'; import icUpload from '../assets/header/icon-upload.svg'; import imgTicket from '../assets/ticket.svg'; import { useNavigate } from "react-router-dom"; -import { ChangeEvent, KeyboardEvent, useContext, useEffect, useRef, useState } from "react"; +import { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from "react"; import CategoryMenu from './CategoryMenu'; import { useModalContext } from './Modal/context/ModalContext'; import SplashModal from '../pages/login/components/SplashModal'; @@ -21,7 +21,8 @@ import { ReactComponent as IcList } from '../assets/icList.svg'; import icDel from '../assets/icDel.svg'; import axiosInstance from '../apis/axiosInstance'; import { TSearch } from '../types/searchKeywords'; -import { AuthContext, useAuth } from '../context/AuthContext'; +import { useAuth } from '../context/AuthContext'; +import { useIsSearchCompleted } from '../store/store'; const ResponsiveHeader = () => { const navigate = useNavigate(); @@ -34,10 +35,8 @@ const ResponsiveHeader = () => { const categoryRef = useRef(null); const [hotKeywords, setHotKeywords] = useState([]); const [recentKeywords, setRecentKeywords] = useState([]); - const [dummy, setDummy] = useState([ - '애플워치', '아이폰', '에어팟', '안경', '코트' - ]); const { isAuthenticated, logout } = useAuth(); + const isSearchCompleted = useIsSearchCompleted(v=>v.isSearchCompleted); const getSearch = async () => { const { data }:{data:TSearch} = await axiosInstance.get( @@ -45,11 +44,12 @@ const ResponsiveHeader = () => { : '/api/permit/search' ); - // console.log('getSearch:', data); + console.log('recentSearch:', data.result.recentSearch); setHotKeywords(data.result.popularSearch); setRecentKeywords(data.result.recentSearch); }; - const delSearch = async () => await axiosInstance.delete('/api/member/search'); + const delSearch = async (keyword:string) => + await axiosInstance.delete(`/api/member/search?keyword=${keyword}`); const handleCategoryOut = (e:MouseEvent) => { const currentCategoryRef = categoryRef.current; @@ -71,24 +71,15 @@ const ResponsiveHeader = () => { }; const handleSearchEnter = (e:KeyboardEvent) => { if (e.key === "Enter") { - if (isAuthenticated) { - axiosInstance.post('/api/member/search', searchText); - // formData.append('recentSearch', searchText); - // fetch(`/api/member/search`, { - // method: "POST", - // body: formData, - // }); - } navigate(`/search/${searchText}`); setIsSearchClicked(false); - } - } + }; + }; const handleDelKeyword = (keyword:string) => { // delSearch(): 해당 키워드 서버에서 삭제 - setDummy(prev => prev.filter(v => v!==keyword)); - console.log(dummy); - } + delSearch(keyword).then(_=>getSearch()); + }; const handleOpenModal = () => { openModal(({ onClose }) => ); @@ -101,7 +92,12 @@ const ResponsiveHeader = () => { // 시작하자마자 호출될 API useEffect(() => { getSearch(); - }, []); + }, [isAuthenticated]); + + // 검색할 때마다 최신 검색어 갱신 + useEffect(() => { + getSearch(); + }, [isSearchCompleted]); useEffect(() => { document.addEventListener('mousedown', handleClickOutside); diff --git a/src/pages/homepage/homePage.tsx b/src/pages/homepage/homePage.tsx index ef8b2ce..9654530 100644 --- a/src/pages/homepage/homePage.tsx +++ b/src/pages/homepage/homePage.tsx @@ -41,7 +41,7 @@ const HomePage: React.FC = () => { } }; - fetchHomeData(); + // fetchHomeData(); }, []); if (!homeData) return
Loading...
; diff --git a/src/pages/raffleList/SearchResultPage.tsx b/src/pages/raffleList/SearchResultPage.tsx index 4c97f09..621c084 100644 --- a/src/pages/raffleList/SearchResultPage.tsx +++ b/src/pages/raffleList/SearchResultPage.tsx @@ -1,9 +1,11 @@ import { useState, useEffect, useRef } from 'react'; -import { useParams, useLocation } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import styled from 'styled-components'; import ProductCard from '../../components/ProductCard'; import RaffleProps from '../../components/RaffleProps'; import axiosInstance from '../../apis/axiosInstance'; +import { useAuth } from '../../context/AuthContext'; +import { useIsSearchCompleted } from '../../store/store'; const SearchResultPage: React.FC = () => { const { type } = useParams<{ type?: string }>(); @@ -12,15 +14,21 @@ const SearchResultPage: React.FC = () => { const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isLoading, setIsLoading] = useState(false); + const { isAuthenticated } = useAuth(); + const setIsCompleted = useIsSearchCompleted(v=>v.setIsSearchCompleted); const fetchMoreProducts = async () => { if (!hasMore || isLoading) return; setIsLoading(true); try { - const { data } = await axiosInstance.get('/api/permit/search/raffles', { + const apirequest = isAuthenticated ? '/api/member/search/raffles' + : '/api/permit/search/raffles' + + const { data } = await axiosInstance.get(apirequest, { params: { keyword: type }, }); + setIsCompleted(true); // Zustand 상태 업데이트 const startIndex = (page - 1) * 16; const endIndex = startIndex + 16; @@ -40,7 +48,8 @@ const SearchResultPage: React.FC = () => { setPage((prev) => prev + 1); } catch (error) { console.error('데이터 불러오기 실패:', error); - } finally { + } + finally { setIsLoading(false); } }; diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..51dd32e --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +interface IIsSearchCompleted { + isSearchCompleted: boolean; + setIsSearchCompleted: (v:boolean) => void; +} + +export const useIsSearchCompleted = create((set) => ({ + isSearchCompleted: false, + setIsSearchCompleted: (v) => set({ isSearchCompleted: v }), +})); \ No newline at end of file