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/HeaderIconMenu.tsx b/src/components/HeaderIconMenu.tsx
deleted file mode 100644
index 1054106..0000000
--- a/src/components/HeaderIconMenu.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import styled from "styled-components";
-import media from "../styles/media";
-
-const HeaderIconMenu = () => {
- return (
-
-
-
-
-
-
-
- );
-};
-
-export default HeaderIconMenu;
-
-const Wrapper = styled.div`
- display: flex;
- // column-gap: 1rem;
- justify-content: space-around;
-`
-
-const IconMenu = styled.div`
- background-color: #F4F4F4;
- border-radius: 100%;
- box-sizing: border-box;
- ${media.medium`
- width: 84.302px;
- height: 86px;
- margin: 39px 0 55px 0;
- `}
- ${media.small`
- width: 58.815px;
- height: 60px;
- margin: 15px 0 22px 0;
- `}
-`
\ No newline at end of file
diff --git a/src/components/ResponsiveHeader.tsx b/src/components/ResponsiveHeader.tsx
index 2843095..c826435 100644
--- a/src/components/ResponsiveHeader.tsx
+++ b/src/components/ResponsiveHeader.tsx
@@ -12,17 +12,17 @@ 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, 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 = ['애플워치','애플워치','애플워치','애플워치',
- '애플워치','애플워치','애플워치','애플워치','애플워치','애플워치',
-];
+import axiosInstance from '../apis/axiosInstance';
+import { TSearch } from '../types/searchKeywords';
+import { useAuth } from '../context/AuthContext';
+import { useIsSearchCompleted } from '../store/store';
const ResponsiveHeader = () => {
const navigate = useNavigate();
@@ -33,6 +33,23 @@ const ResponsiveHeader = () => {
const searchRef = useRef(null);
const [searchText, setSearchText] = useState('');
const categoryRef = useRef(null);
+ const [hotKeywords, setHotKeywords] = useState([]);
+ const [recentKeywords, setRecentKeywords] = useState([]);
+ const { isAuthenticated, logout } = useAuth();
+ const isSearchCompleted = useIsSearchCompleted(v=>v.isSearchCompleted);
+
+ const getSearch = async () => {
+ const { data }:{data:TSearch} = await axiosInstance.get(
+ isAuthenticated ? '/api/member/search'
+ : '/api/permit/search'
+ );
+
+ console.log('recentSearch:', data.result.recentSearch);
+ setHotKeywords(data.result.popularSearch);
+ setRecentKeywords(data.result.recentSearch);
+ };
+ const delSearch = async (keyword:string) =>
+ await axiosInstance.delete(`/api/member/search?keyword=${keyword}`);
const handleCategoryOut = (e:MouseEvent) => {
const currentCategoryRef = categoryRef.current;
@@ -52,15 +69,36 @@ const ResponsiveHeader = () => {
const handleSearchInput = (e: ChangeEvent) => {
setSearchText(e.target.value);
};
+ const handleSearchEnter = (e:KeyboardEvent) => {
+ if (e.key === "Enter") {
+ navigate(`/search/${searchText}`);
+ setIsSearchClicked(false);
+ };
+ };
+
+ const handleDelKeyword = (keyword:string) => {
+ // delSearch(): 해당 키워드 서버에서 삭제
+ delSearch(keyword).then(_=>getSearch());
+ };
const handleOpenModal = () => {
openModal(({ onClose }) => );
};
-
const onClickLoginBtn = () => {
- setIsLoggedIn((prev) => !prev); // 클릭할 때마다 로그인/로그아웃 상태 변경
+ if (!isAuthenticated) handleOpenModal();
+ else logout();
};
+ // 시작하자마자 호출될 API
+ useEffect(() => {
+ getSearch();
+ }, [isAuthenticated]);
+
+ // 검색할 때마다 최신 검색어 갱신
+ useEffect(() => {
+ getSearch();
+ }, [isSearchCompleted]);
+
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('mousedown', handleCategoryOut);
@@ -74,8 +112,8 @@ const ResponsiveHeader = () => {
<>
-
- {isLoggedIn ? '로그아웃' : '로그인'}
+
+ {isAuthenticated ? '로그아웃' : '로그인'}
@@ -104,37 +142,46 @@ const ResponsiveHeader = () => {
setIsSearchClicked(true)}
value={searchText}
onChange={handleSearchInput}
+ onKeyUp={handleSearchEnter}
/>
-
+ {isLoggedIn===false
+ ?
최근 검색
- {recentKeywords.map((v,_) => (
+ {recentKeywords.length!==0 ?
+ recentKeywords.map((v,_) => (
{v}
-
+ handleDelKeyword(v)}
+ />
- ))}
+ ))
+ : 최근 검색 내역이 없습니다.
+ }
+ : <>>
+ }
현재 인기있는 검색어
- {recentKeywords.map((v,_) => (
+ {hotKeywords.map((v,_) => (
{v}
@@ -172,7 +219,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;
@@ -317,15 +364,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'
@@ -384,6 +432,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/layout/RootLayout.tsx b/src/layout/RootLayout.tsx
index b839a81..75096bb 100644
--- a/src/layout/RootLayout.tsx
+++ b/src/layout/RootLayout.tsx
@@ -1,14 +1,11 @@
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 (
- {/* */}
);
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
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