diff --git a/.gitignore b/.gitignore index 475036a..9ee77e8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ dist-ssr *.sw? .vercel +.env*.local diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index c2fd2eb..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"; @@ -29,10 +27,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(); @@ -40,25 +39,26 @@ const HomePage = () => { const maxCompany = companyData?.companies.slice(0, 8) ?? []; const [searchParams, setSearchParams] = useSearchParams(); - const searchQuery = searchParams.get("search"); - const debouncedInput = useDebounce(searchQuery, 200); - const isSearching = debouncedInput && debouncedInput.trim() !== ""; - const { user } = useUserStore(); - const isLogin = !!user?.accessToken; - const navigate = useNavigate(); + const searchQuery = searchParams.get("search") ?? ""; + const selectedTab = Number(searchParams.get("tab") ?? 0); + + useEffect(() => { + if (!searchParams.get("tab") && !searchParams.get("search")) { + setSearchParams({ tab: "0" }, { replace: true }); + } + }, []); + + const isSearching = !!searchQuery.trim(); const handleTabChange = (tab: number) => { if (tab === 1 && !isLogin) { toast.info("로그인이 필요한 서비스입니다.", { icon: login으로 이동, }); - navigate("/login"); - return; } - setSearchParams({}); - setSelectedTab(tab); + setSearchParams({ tab: String(tab) }, { replace: true }); }; useEffect(() => { @@ -72,7 +72,7 @@ const HomePage = () => { {isSearching - ? `"${debouncedInput}" | TechFork` + ? `"${searchQuery}" | TechFork` : `TechFork | ${TAB_MAP[selectedTab]}`} @@ -103,7 +103,7 @@ const HomePage = () => { <> }> - + 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 = () => {

-

이용약관

및 -

개인정보취급방침에

+

+ + 이용약관 + +

+ 및 +

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

동의합니다.

diff --git a/src/widgets/header/SystemHeader.tsx b/src/widgets/header/SystemHeader.tsx index 1c2a54d..a53a865 100644 --- a/src/widgets/header/SystemHeader.tsx +++ b/src/widgets/header/SystemHeader.tsx @@ -1,97 +1,51 @@ 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 { 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"; +import { useEffect, useState } from "react"; +import { useDebounce } from "@/shared/lib/useDebounce"; export const SystemHeader = () => { const navigate = useNavigate(); - const [userModal, setUserModal] = useState(false); - const modalRef = useRef(null); 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(""); - - 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 [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]); - + setInput(urlSearch); + }, [urlSearch]); + const debouncedInput = useDebounce(input, 300); useEffect(() => { - if (input === "") { - if (searchParams.get("search")) { - navigate("/", { replace: true }); - } - return; - } - if (input !== searchParams.get("search")) { - navigate(`/?search=${input}`, { replace: true }); - } - }, [input, navigate]); + const trimmed = debouncedInput.trim(); + const currentSearch = searchParams.get("search") ?? ""; - useEffect(() => { - const handleClick = (e: MouseEvent) => { - if (!modalRef.current?.contains(e.target as Node)) - return setUserModal(false); - }; + if (trimmed === currentSearch) return; - document.addEventListener("click", handleClick); - return () => { - //cleanup - document.removeEventListener("click", handleClick); - }; - }, []); - - useEffect(() => { - if (userModal) { - setInput(""); + if (!trimmed) { + navigate("/?tab=0", { replace: true }); + } else { + navigate(`/?search=${encodeURIComponent(trimmed)}`, { replace: true }); } - }, [userModal]); + }, [debouncedInput]); + + const handleLogoClick = () => { + resetCompanies(); + setInput(""); + navigate("/?tab=0"); + }; return (
@@ -100,10 +54,7 @@ export const SystemHeader = () => { src={isDark ? "/dark_logo.svg" : "/logo.svg"} alt="로고" className="w-35 h-12 cursor-pointer" - onClick={() => { - navigate("/"); - setInput(""); - }} + onClick={handleLogoClick} fetchPriority="high" />
@@ -151,7 +102,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, + }; +};