diff --git a/components/Link/LinkCard.tsx b/components/Link/LinkCard.tsx index 91c7dc2..878c50e 100644 --- a/components/Link/LinkCard.tsx +++ b/components/Link/LinkCard.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { MouseEvent, useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; import { useLinkCardStore } from "@/store/useLinkCardStore"; import { ensureAbsoluteUrl } from "@/lib/utils"; @@ -44,7 +44,8 @@ const LinkCard = ({ info }: LinkCardProps) => { }); // 즐겨찾기 버튼 클릭 시 호출되는 함수 - const handleFavoriteToggle = async () => { + const handleFavoriteToggle = async (e: MouseEvent) => { + e.stopPropagation(); setIsSubscribed((prev) => !prev); try { updateFavorite(info.id, !isSubscribed); @@ -54,7 +55,10 @@ const LinkCard = ({ info }: LinkCardProps) => { }; // dropdown 버튼 - const toggleDropdown = () => setIsDropdownOpen((prev) => !prev); + const toggleDropdown = (e: MouseEvent) => { + e.stopPropagation(); + setIsDropdownOpen((prev) => !prev); + }; const handleModalOpen = ( type: "EditLink" | "DeleteLinkModal", @@ -64,6 +68,12 @@ const LinkCard = ({ info }: LinkCardProps) => { openModal(type, { link, linkId }); }; + const handleNavigate = (url: string) => { + if (!isDropdownOpen) + window.location.href = + url.slice(0, 4) === "http" ? url : `https://${url}`; + }; + const dropdownItems = [ { label: "수정하기", @@ -76,7 +86,10 @@ const LinkCard = ({ info }: LinkCardProps) => { ]; return ( -
+
handleNavigate(info.url)} + >
{
)}
-
+
handleNavigate(info.url)} + > {info.description || "설명"}
{formattedDate || "2024.11.06"}
diff --git a/hooks/useFetchLinks.tsx b/hooks/useFetchLinks.tsx index aca435a..b49c9e1 100644 --- a/hooks/useFetchLinks.tsx +++ b/hooks/useFetchLinks.tsx @@ -6,15 +6,16 @@ import useViewport from "./useViewport"; // 링크페이지의 query가 바뀌면 그에 맞는 링크들을 보여주는 훅 const useFetchLinks = ( - setLinkCardList: React.Dispatch>, - setTotalCount?: React.Dispatch>, + setLinkCardList: (list: LinkData[], totalCount: number) => void, + setIsLoading: React.Dispatch>, query?: ParsedUrlQuery, pathname?: string ) => { - const { isTablet } = useViewport(); + const { isMobile, isTablet } = useViewport(); useEffect(() => { const fetchLinks = async () => { + setIsLoading(true); // 경로에 따라 API 엔드포인트 분기 let endpoint = pathname === "/favorite" @@ -26,18 +27,16 @@ const useFetchLinks = ( const res = await proxy.get(endpoint, { params: { page: query?.page, - pageSize: isTablet ? 6 : 10, + pageSize: isMobile ? 10 : isTablet ? 6 : 9, search: query?.search, }, }); - console.log("query가 바뀌었을 때 다시 받아온 리스트:", res.data.list); - setLinkCardList(res.data.list); - { - setTotalCount && setTotalCount(res.data.totalCount); - } + console.log("useFetchLinks 함수에서 다시 받아온 리스트:", res.data.list); + setLinkCardList(res.data.list, res.data.totalCount); + setIsLoading(false); }; if (query) fetchLinks(); - }, [setLinkCardList, query, isTablet]); + }, [setLinkCardList, query, isTablet, isMobile]); }; export default useFetchLinks; diff --git a/hooks/useFolderName.tsx b/hooks/useFolderName.tsx new file mode 100644 index 0000000..4296aaa --- /dev/null +++ b/hooks/useFolderName.tsx @@ -0,0 +1,24 @@ +import { getFolder } from "@/lib/api/folder"; +import { useEffect, useState } from "react"; + +const useFolderName = (folderId: string | string[] | undefined) => { + const [folderName, setFolderName] = useState("전체"); + + useEffect(() => { + if (!folderId) return; + + const fetchFolderInfo = async () => { + try { + const res = await getFolder(folderId as string); + setFolderName(res.name); + } catch (error) { + console.error("Failed to fetch folder info:", error); + } + }; + fetchFolderInfo(); + }, [folderId]); + + return [folderName, setFolderName]; +}; + +export default useFolderName; diff --git a/lib/api/fetchProxy.ts b/lib/api/fetchProxy.ts deleted file mode 100644 index 9953642..0000000 --- a/lib/api/fetchProxy.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { proxy } from "./axiosInstanceApi"; - -// SSR에서 proxy로 요청 보낼 때 사용하는 로직 추상화 -const fetchProxy = async (endpoint: string, req: any) => { - const headers = req ? { Cookie: req.headers.cookie } : undefined; - const response = await proxy.get(endpoint, { headers }); - return response.data; -}; - -export default fetchProxy; diff --git a/lib/toastMessage.ts b/lib/toastMessage.ts index c404851..affd653 100644 --- a/lib/toastMessage.ts +++ b/lib/toastMessage.ts @@ -31,6 +31,7 @@ const toastMessages = { sameFolderName: "같은 이름으로는 수정할 수 없습니다", invalidLink: "잘못된 링크 형식입니다", invalidLinkCount: "폴더 정보를 받아오는데 실패했습니다", + needLogin: "로그인 후 이용해주세요", }, }; diff --git a/pages/link/index.tsx b/pages/link/index.tsx index d265502..63f6736 100644 --- a/pages/link/index.tsx +++ b/pages/link/index.tsx @@ -6,6 +6,7 @@ import { LinkData } from "@/types/linkTypes"; import { FolderData } from "@/types/folderTypes"; import { Modal } from "@/components/modal/modalManager/ModalManager"; import { SearchInput } from "../../components/Search/SearchInput"; +import { useLinkCardStore } from "@/store/useLinkCardStore"; import axiosInstance from "@/lib/api/axiosInstanceApi"; import useModalStore from "@/store/useModalStore"; import Pagination from "@/components/Pagination"; @@ -20,6 +21,8 @@ import LinkCard from "@/components/Link/LinkCard"; import RenderEmptyLinkMessage from "@/components/Link/RenderEmptyLinkMessage"; import useFetchLinks from "@/hooks/useFetchLinks"; import useViewport from "@/hooks/useViewport"; +import useFolderName from "@/hooks/useFolderName"; +import LoadingSpinner from "@/components/LoadingSpinner"; interface LinkPageProps { linkList: LinkData[]; @@ -35,6 +38,16 @@ export const getServerSideProps = async ( const cookies = parse(req.headers.cookie || ""); const accessToken = cookies.accessToken; + // accessToken이 없으면 클라이언트에서 실행될 때 /login 페이지로 이동시킴. + if (!accessToken) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + const fetchData = async (endpoint: string) => { const res = await axiosInstance.get(endpoint, { headers: { @@ -67,13 +80,14 @@ const LinkPage = ({ const { search, folder } = router.query; const { isOpen } = useModalStore(); const { isMobile } = useViewport(); - const [linkCardList, setLinkCardList] = useState(initialLinkList); + const { totalCount, linkCardList, setLinkCardList } = + useLinkCardStore.getState(); + const [isLoading, setIsLoading] = useState(false); + const [folderName] = useFolderName(folder); const [folderList, setFolderList] = useState(initialFolderList); - const [totalCount, setTotalCount] = useState(initialTotalCount); // 링크페이지의 query가 바뀌면 새로운 리스트로 업데이트 해주는 훅 - useFetchLinks(setLinkCardList, setTotalCount, router.query, router.pathname); - + useFetchLinks(setLinkCardList, setIsLoading, router.query, router.pathname); console.log(linkCardList); return ( @@ -90,23 +104,27 @@ const LinkPage = ({ {!isMobile && }
-

유용한 글

{folder && ( - + <> +

{folderName as string}

+ + )}
- {linkCardList ? ( + {isLoading ? ( + // 로딩 상태일 때 로딩 스피너 표시 + ) : linkCardList.length !== 0 ? ( <> {linkCardList.map((link) => ( ))} - + ) : (