Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ dist-ssr
*.sw?

.vercel
.env*.local
32 changes: 16 additions & 16 deletions src/pages/home/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -29,36 +27,38 @@ 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();

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: <img src={Alert} alt="login으로 이동" />,
});

navigate("/login");

return;
}
setSearchParams({});
setSelectedTab(tab);
setSearchParams({ tab: String(tab) }, { replace: true });
};

useEffect(() => {
Expand All @@ -72,7 +72,7 @@ const HomePage = () => {
<Helmet>
<title>
{isSearching
? `"${debouncedInput}" | TechFork`
? `"${searchQuery}" | TechFork`
: `TechFork | ${TAB_MAP[selectedTab]}`}
</title>
<meta property="og:title" content="TechFork | 기업 기술 블로그 모음" />
Expand Down Expand Up @@ -103,7 +103,7 @@ const HomePage = () => {
<>
<ErrorBoundary>
<Suspense fallback={<SkeletonList />}>
<SearchPostList query={debouncedInput ?? ""} />
<SearchPostList query={searchQuery ?? ""} />
</Suspense>
</ErrorBoundary>
</>
Expand Down
21 changes: 19 additions & 2 deletions src/pages/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,25 @@ const Onboarding = () => {
</div>

<p className="body-r-14 flex gap-1 font-alternative">
<p className="text-blue-500">이용약관</p>및
<p className="text-blue-500">개인정보취급방침에</p>
<p className="text-blue-500">
<a
href="https://lily-reptile-b33.notion.site/334d0aa99dcf8066a9abc2e9a2aca4fd?source=copy_link"
target="_blank"
rel="noopener noreferrer"
>
이용약관
</a>
</p>
<p className="text-blue-500">
<a
target="_blank"
rel="noopener noreferrer"
href="https://lily-reptile-b33.notion.site/334d0aa99dcf8083bd3bf5e51e609a54?source=copy_link"
>
개인정보취급방침에
</a>
</p>
동의합니다.
</p>
</div>
Expand Down
109 changes: 32 additions & 77 deletions src/widgets/header/SystemHeader.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement | null>(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<string>("");

const handleLogout = async () => {
try {
await postLogout();

toast.info(`로그아웃 되었습니다.`, {
icon: <img src={Logout} alt="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 (
<header className={cn("max-w-480 mx-auto gap-2 pb-5 pt-7 px-14 ")}>
Expand All @@ -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"
/>
<div className="w-160 flex bg-bgPrimary rounded-lg border border-bgNormal px-3">
Expand Down Expand Up @@ -151,7 +102,11 @@ export const SystemHeader = () => {
>
{MYPAGE_NAV.map(item => {
return (
<div className="p-4" onClick={() => handleNavClick(item)}>
<div
key={item.name}
className="p-4"
onClick={() => handleNavClick(item)}
>
{item.name}
</div>
);
Expand Down
62 changes: 62 additions & 0 deletions src/widgets/header/model/useUserMenu.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement | null>(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: <img src={Logout} alt="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,
};
};
Loading