From 29d85e90ae5577efbda83e24254fbf0a60c91b78 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 24 Mar 2026 19:57:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20tab=EC=9D=84=20params=EB=A1=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/HomePage.tsx | 26 +++++++++++++++++--------- src/widgets/header/SystemHeader.tsx | 16 +++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index c2fd2eb..bdd3dda 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -29,10 +29,11 @@ const SearchPostList = lazy(() => ); const HomePage = () => { - const [selectedTab, setSelectedTab] = useState(0); const [modal, setModal] = useState(false); const { companies, toggleCompany, resetCompanies } = useCompanyStore(); - + const { user } = useUserStore(); + const isLogin = !!user?.accessToken; + const navigate = useNavigate(); const { data: companyData } = useGetCompany(); const { mutate: postRecommendList, isPending: isRefreshing } = usePostRecommendPostList(); @@ -41,24 +42,31 @@ const HomePage = () => { const [searchParams, setSearchParams] = useSearchParams(); const searchQuery = searchParams.get("search"); + const selectedTab = Number(searchParams.get("tab") ?? 0); + + useEffect(() => { + if (!searchParams.get("tab")) { + const tabParams = new URLSearchParams(searchParams); + tabParams.set("tab", "0"); + setSearchParams(tabParams, { replace: true }); + } + }, [searchParams, setSearchParams]); + const debouncedInput = useDebounce(searchQuery, 200); const isSearching = debouncedInput && debouncedInput.trim() !== ""; - const { user } = useUserStore(); - const isLogin = !!user?.accessToken; - const navigate = useNavigate(); const handleTabChange = (tab: number) => { if (tab === 1 && !isLogin) { toast.info("로그인이 필요한 서비스입니다.", { icon: login으로 이동, }); - navigate("/login"); - return; } - setSearchParams({}); - setSelectedTab(tab); + const nextParams = new URLSearchParams(searchParams); + nextParams.delete("search"); + nextParams.set("tab", String(tab)); + setSearchParams(nextParams); }; useEffect(() => { diff --git a/src/widgets/header/SystemHeader.tsx b/src/widgets/header/SystemHeader.tsx index 1c2a54d..51e0cff 100644 --- a/src/widgets/header/SystemHeader.tsx +++ b/src/widgets/header/SystemHeader.tsx @@ -29,6 +29,7 @@ export const SystemHeader = () => { const [searchParams] = useSearchParams(); const [input, setInput] = useState(""); + //api const handleLogout = async () => { try { await postLogout(); @@ -46,6 +47,7 @@ export const SystemHeader = () => { } }; + //로그아웃 const handleNavClick = (item: { name: string; nav?: string }) => { if (item.name === "로그아웃") { handleLogout(); @@ -87,11 +89,11 @@ export const SystemHeader = () => { }; }, []); - useEffect(() => { - if (userModal) { - setInput(""); - } - }, [userModal]); + // useEffect(() => { + // if (userModal) { + // setInput(""); + // } + // }, [userModal]); return (
@@ -101,8 +103,8 @@ export const SystemHeader = () => { alt="로고" className="w-35 h-12 cursor-pointer" onClick={() => { - navigate("/"); - setInput(""); + resetCompanies(); + navigate("/?tab=0"); }} fetchPriority="high" /> From 830be3c792b3b02dbb2d98017b586d5b4f8f139c Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 24 Mar 2026 20:03:45 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=EA=B2=80=EC=83=89=EC=8B=9C=20tab?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/HomePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index bdd3dda..5ba57f2 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -45,7 +45,7 @@ const HomePage = () => { const selectedTab = Number(searchParams.get("tab") ?? 0); useEffect(() => { - if (!searchParams.get("tab")) { + if (!searchParams.get("tab") && !searchParams.get("search")) { const tabParams = new URLSearchParams(searchParams); tabParams.set("tab", "0"); setSearchParams(tabParams, { replace: true }); From ecf70961cf94ddc956df150438fe8e9d04a26dc8 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 24 Mar 2026 20:04:24 +0900 Subject: [PATCH 3/7] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20modal=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/header/SystemHeader.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/widgets/header/SystemHeader.tsx b/src/widgets/header/SystemHeader.tsx index 51e0cff..2d2b95e 100644 --- a/src/widgets/header/SystemHeader.tsx +++ b/src/widgets/header/SystemHeader.tsx @@ -89,12 +89,6 @@ export const SystemHeader = () => { }; }, []); - // useEffect(() => { - // if (userModal) { - // setInput(""); - // } - // }, [userModal]); - return (
From 270e99231cd66d70dc2fe8badc77d777aaefcc2b Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 3 Apr 2026 14:50:36 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20user=20model=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/header/SystemHeader.tsx | 97 ++++++++++-------------- src/widgets/header/model/useUserMenu.tsx | 62 +++++++++++++++ 2 files changed, 100 insertions(+), 59 deletions(-) create mode 100644 src/widgets/header/model/useUserMenu.tsx diff --git a/src/widgets/header/SystemHeader.tsx b/src/widgets/header/SystemHeader.tsx index 2d2b95e..b2311f1 100644 --- a/src/widgets/header/SystemHeader.tsx +++ b/src/widgets/header/SystemHeader.tsx @@ -1,61 +1,31 @@ import Search from "@/assets/icons/search.svg"; import User from "@/assets/images/user.png"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import Alert from "@/assets/icons/alert2.svg"; -import Logout from "@/assets/icons/confirm.svg"; import { useThemeToggle } from "@/shared/lib/useThemeToggle"; -import useUserStore from "@/shared/model/useUserStore"; import { MYPAGE_NAV } from "@/features/mypage"; import { useCompanyStore } from "@/features/home"; import { useGetMyProfile } from "@/shared/api/my"; -import { postLogout } from "@/features/Login"; import { cn } from "@/shared/lib/cn"; import { Button } from "@/shared/ui/button/Button"; +import { useUserMenu } from "./model/useUserMenu"; export const SystemHeader = () => { const navigate = useNavigate(); - const [userModal, setUserModal] = useState(false); - const modalRef = useRef(null); + // const location = useLocation(); const { isDark } = useThemeToggle(); - const { user, logout } = useUserStore(); - const { resetCompanies, companies } = useCompanyStore(); - console.log(companies); - const isLogin = !!user?.accessToken; + const { resetCompanies } = useCompanyStore(); + const { isLogin, userModal, setUserModal, modalRef, handleNavClick } = + useUserMenu(); //모달 관리 const { data } = useGetMyProfile(isLogin); const [searchParams] = useSearchParams(); - const [input, setInput] = useState(""); - - //api - const handleLogout = async () => { - try { - await postLogout(); - - toast.info(`로그아웃 되었습니다.`, { - icon: logout, - }); - } catch (error) { - console.error("로그아웃 실패:", error); - } finally { - logout(); - resetCompanies(); - setUserModal(false); - navigate("/"); - } - }; - - //로그아웃 - const handleNavClick = (item: { name: string; nav?: string }) => { - if (item.name === "로그아웃") { - handleLogout(); - } else if (item.nav) { - setUserModal(false); - navigate(item.nav); - } - }; + const [input, setInput] = useState( + () => searchParams.get("search") || "", + ); useEffect(() => { const searchQuery = searchParams.get("search") || ""; @@ -64,30 +34,35 @@ export const SystemHeader = () => { } }, [searchParams]); + // useEffect(() => { + // if (location.pathname !== "/") return; + + // if (input === "") { + // if (searchParams.get("search")) { + // navigate("/", { replace: true }); + // } + // return; + // } + // if (input !== searchParams.get("search")) { + // navigate(`/?search=${input}`, { replace: true }); + // } + // }, [input, navigate, searchParams]); + useEffect(() => { - if (input === "") { - if (searchParams.get("search")) { - navigate("/", { replace: true }); + const trimmed = input.trim(); + const currentSearch = searchParams.get("search") || ""; + + if (!trimmed) { + if (location.pathname === "/" && currentSearch) { + navigate("/?tab=0", { replace: true }); } return; } - if (input !== searchParams.get("search")) { - navigate(`/?search=${input}`, { replace: true }); - } - }, [input, navigate]); - useEffect(() => { - const handleClick = (e: MouseEvent) => { - if (!modalRef.current?.contains(e.target as Node)) - return setUserModal(false); - }; - - document.addEventListener("click", handleClick); - return () => { - //cleanup - document.removeEventListener("click", handleClick); - }; - }, []); + if (trimmed !== currentSearch) { + navigate(`/?search=${encodeURIComponent(trimmed)}`, { replace: true }); + } + }, [input, location.pathname, navigate, searchParams]); return (
@@ -147,7 +122,11 @@ export const SystemHeader = () => { > {MYPAGE_NAV.map(item => { return ( -
handleNavClick(item)}> +
handleNavClick(item)} + > {item.name}
); diff --git a/src/widgets/header/model/useUserMenu.tsx b/src/widgets/header/model/useUserMenu.tsx new file mode 100644 index 0000000..fcc67b9 --- /dev/null +++ b/src/widgets/header/model/useUserMenu.tsx @@ -0,0 +1,62 @@ +import Logout from "@/assets/icons/confirm.svg"; +import { postLogout } from "@/features/Login"; +import { useCompanyStore } from "@/features/home"; +import useUserStore from "@/shared/model/useUserStore"; +import { useEffect, useRef, useState } from "react"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; + +export const useUserMenu = () => { + const navigate = useNavigate(); + const modalRef = useRef(null); + const [userModal, setUserModal] = useState(false); + + const { logout, user } = useUserStore(); + const { resetCompanies } = useCompanyStore(); + const isLogin = !!user?.accessToken; + + const handleLogout = async () => { + try { + await postLogout(); + toast.info("로그아웃 되었습니다.", { + icon: logout, + }); + } finally { + logout(); + resetCompanies(); + setUserModal(false); + navigate("/"); + } + }; + + const handleNavClick = (item: { name: string; nav?: string }) => { + if (item.name === "로그아웃") { + handleLogout(); + return; + } + + if (item.nav) { + setUserModal(false); + navigate(item.nav); + } + }; + + useEffect(() => { + const handleClick = (e: MouseEvent) => { + if (!modalRef.current?.contains(e.target as Node)) { + setUserModal(false); + } + }; + + document.addEventListener("click", handleClick); + return () => document.removeEventListener("click", handleClick); + }, []); + + return { + isLogin, + userModal, + setUserModal, + modalRef, + handleNavClick, + }; +}; From e340aa51ab33731836eb49e2082b3b0594705b0e Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 3 Apr 2026 15:17:34 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20debounce=20Input=EC=9D=84=20header?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B4=80=EB=A6=AC=20=EB=B0=8F=20IME?= =?UTF-8?q?=EC=A1=B0=ED=95=A9=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/HomePage.tsx | 22 +++------- src/widgets/header/SystemHeader.tsx | 66 ++++++++++------------------- 2 files changed, 30 insertions(+), 58 deletions(-) diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 5ba57f2..b6504bb 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -7,8 +7,6 @@ import { useGetCompany, usePostRecommendPostList, } from "@/features/home"; -import { useDebounce } from "@/shared/lib/useDebounce"; - import { ErrorBoundary } from "@/shared/ui/ErrorBoundary"; import { Loading } from "@/shared/ui/Loading"; import { SkeletonList } from "@/shared/ui/SkeletonList"; @@ -41,19 +39,16 @@ const HomePage = () => { const maxCompany = companyData?.companies.slice(0, 8) ?? []; const [searchParams, setSearchParams] = useSearchParams(); - const searchQuery = searchParams.get("search"); + const searchQuery = searchParams.get("search") ?? ""; const selectedTab = Number(searchParams.get("tab") ?? 0); useEffect(() => { if (!searchParams.get("tab") && !searchParams.get("search")) { - const tabParams = new URLSearchParams(searchParams); - tabParams.set("tab", "0"); - setSearchParams(tabParams, { replace: true }); + setSearchParams({ tab: "0" }, { replace: true }); } - }, [searchParams, setSearchParams]); + }, []); - const debouncedInput = useDebounce(searchQuery, 200); - const isSearching = debouncedInput && debouncedInput.trim() !== ""; + const isSearching = !!searchQuery.trim(); const handleTabChange = (tab: number) => { if (tab === 1 && !isLogin) { @@ -63,10 +58,7 @@ const HomePage = () => { navigate("/login"); return; } - const nextParams = new URLSearchParams(searchParams); - nextParams.delete("search"); - nextParams.set("tab", String(tab)); - setSearchParams(nextParams); + setSearchParams({ tab: String(tab) }, { replace: true }); }; useEffect(() => { @@ -80,7 +72,7 @@ const HomePage = () => { {isSearching - ? `"${debouncedInput}" | TechFork` + ? `"${searchQuery}" | TechFork` : `TechFork | ${TAB_MAP[selectedTab]}`} @@ -111,7 +103,7 @@ const HomePage = () => { <> }> - + diff --git a/src/widgets/header/SystemHeader.tsx b/src/widgets/header/SystemHeader.tsx index b2311f1..a53a865 100644 --- a/src/widgets/header/SystemHeader.tsx +++ b/src/widgets/header/SystemHeader.tsx @@ -1,7 +1,6 @@ import Search from "@/assets/icons/search.svg"; import User from "@/assets/images/user.png"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import Alert from "@/assets/icons/alert2.svg"; import { useThemeToggle } from "@/shared/lib/useThemeToggle"; @@ -11,58 +10,42 @@ import { useGetMyProfile } from "@/shared/api/my"; import { cn } from "@/shared/lib/cn"; import { Button } from "@/shared/ui/button/Button"; import { useUserMenu } from "./model/useUserMenu"; +import { useEffect, useState } from "react"; +import { useDebounce } from "@/shared/lib/useDebounce"; export const SystemHeader = () => { const navigate = useNavigate(); - // const location = useLocation(); const { isDark } = useThemeToggle(); - const { resetCompanies } = useCompanyStore(); const { isLogin, userModal, setUserModal, modalRef, handleNavClick } = - useUserMenu(); //모달 관리 - + useUserMenu(); const { data } = useGetMyProfile(isLogin); - const [searchParams] = useSearchParams(); - const [input, setInput] = useState( - () => searchParams.get("search") || "", - ); + const [searchParams] = useSearchParams(); //tab + const [input, setInput] = useState(() => searchParams.get("search") ?? ""); + const urlSearch = searchParams.get("search") ?? ""; useEffect(() => { - const searchQuery = searchParams.get("search") || ""; - if (input !== searchQuery) { - setInput(searchQuery); - } - }, [searchParams]); - - // useEffect(() => { - // if (location.pathname !== "/") return; - - // if (input === "") { - // if (searchParams.get("search")) { - // navigate("/", { replace: true }); - // } - // return; - // } - // if (input !== searchParams.get("search")) { - // navigate(`/?search=${input}`, { replace: true }); - // } - // }, [input, navigate, searchParams]); - + setInput(urlSearch); + }, [urlSearch]); + const debouncedInput = useDebounce(input, 300); useEffect(() => { - const trimmed = input.trim(); - const currentSearch = searchParams.get("search") || ""; + const trimmed = debouncedInput.trim(); + const currentSearch = searchParams.get("search") ?? ""; - if (!trimmed) { - if (location.pathname === "/" && currentSearch) { - navigate("/?tab=0", { replace: true }); - } - return; - } + if (trimmed === currentSearch) return; - if (trimmed !== currentSearch) { + if (!trimmed) { + navigate("/?tab=0", { replace: true }); + } else { navigate(`/?search=${encodeURIComponent(trimmed)}`, { replace: true }); } - }, [input, location.pathname, navigate, searchParams]); + }, [debouncedInput]); + + const handleLogoClick = () => { + resetCompanies(); + setInput(""); + navigate("/?tab=0"); + }; return (
@@ -71,10 +54,7 @@ export const SystemHeader = () => { src={isDark ? "/dark_logo.svg" : "/logo.svg"} alt="로고" className="w-35 h-12 cursor-pointer" - onClick={() => { - resetCompanies(); - navigate("/?tab=0"); - }} + onClick={handleLogoClick} fetchPriority="high" />
From e7ac88a2aaac650d2e13d2872565928126382aa8 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 3 Apr 2026 15:46:25 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=EC=95=BD=EA=B4=80=20notion=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/onboarding/Onboarding.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx index 681ee07..1d23490 100644 --- a/src/pages/onboarding/Onboarding.tsx +++ b/src/pages/onboarding/Onboarding.tsx @@ -77,8 +77,25 @@ const Onboarding = () => {

-

이용약관

및 -

개인정보취급방침에

+

+ + 이용약관 + +

+ 및 +

+ + 개인정보취급방침에 + +

동의합니다.

From 0049b113793c83ed9a06e3a7ad1915dff416bfdf Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 3 Apr 2026 16:38:06 +0900 Subject: [PATCH 7/7] feat: deploy --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 475036a..9ee77e8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ dist-ssr *.sw? .vercel +.env*.local