diff --git a/components/Link/LinkCard.tsx b/components/Link/LinkCard.tsx index 4da638a..e5d7cbd 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 "@/util/ensureAbsoluteUrl"; @@ -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 18a5b59..ae0af6c 100644 --- a/hooks/useFetchLinks.tsx +++ b/hooks/useFetchLinks.tsx @@ -1,25 +1,21 @@ -import { useState, useEffect } from "react"; +import { useEffect } from "react"; +import { useRouter } from "next/router"; import { proxy } from "@/lib/api/axiosInstanceApi"; import { LinkData } from "@/types/linkTypes"; -import { ParsedUrlQuery } from "querystring"; import useViewport from "./useViewport"; // 링크 페이지의 query가 바뀌면 그에 맞는 링크들을 보여주는 훅 const useFetchLinks = ( - setLinkCardList: React.Dispatch>, - setTotalCount?: React.Dispatch>, - query?: ParsedUrlQuery, - pathname?: string + setLinkCardList: (list: LinkData[], totalCount: number) => void, + setIsLoading: React.Dispatch> ) => { + const router = useRouter(); + const { query, pathname } = router; const { isTablet } = useViewport(); - const [loading, setLoading] = useState(false); // 로딩 상태 관리 useEffect(() => { const fetchLinks = async () => { - if (isTablet === undefined) return; // isTablet이 정의되지 않았으면 API 호출을 막음 - - setLoading(true); // API 호출 시작 시 로딩 상태 true - + setIsLoading(true); // 경로에 따라 API 엔드포인트 분기 let endpoint = pathname === "/favorite" @@ -35,16 +31,12 @@ const useFetchLinks = ( search: query?.search, }, }); - setLinkCardList(res.data.list); - { - setTotalCount && setTotalCount(res.data.totalCount); - } + setLinkCardList(res.data.list, res.data.totalCount); + setIsLoading(false); }; if (query) fetchLinks(); - }, [setLinkCardList, query, isTablet]); - - return loading; // 로딩 상태 반환 + }, [setLinkCardList, setIsLoading, pathname, query, isTablet]); }; 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/favorite/index.tsx b/pages/favorite/index.tsx index b5bd5fd..479980a 100644 --- a/pages/favorite/index.tsx +++ b/pages/favorite/index.tsx @@ -1,13 +1,13 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; import { GetServerSideProps, GetServerSidePropsContext } from "next"; +import { parse } from "cookie"; import axiosInstance from "@/lib/api/axiosInstanceApi"; import CardsLayout from "@/components/Layout/CardsLayout"; import Container from "@/components/Layout/Container"; import LinkCard from "@/components/Link/LinkCard"; import Pagination from "@/components/Pagination"; import useFetchLinks from "@/hooks/useFetchLinks"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { parse } from "cookie"; import LoadingSpinner from "@/components/LoadingSpinner"; import EmptyFavoriteList from "@/components/Favorite/EmptyFavoriteList"; @@ -62,9 +62,11 @@ const FavoritePage = ({ const router = useRouter(); const [linkCardList, setLinkCardList] = useState(favoriteList); + const [isLoading, setIsLoading] = useState(false); const [totalCount, setTotalCount] = useState(initialTotalCount); - const loading = useFetchLinks(setLinkCardList, setTotalCount, router.query); + useFetchLinks(setLinkCardList, setIsLoading); + // // 마이링크 페이지로 돌아감 const returnButton = () => { @@ -86,7 +88,7 @@ const FavoritePage = ({
{/* 로딩 중일 때 */} - {loading ? ( + {isLoading ? (
diff --git a/pages/link/index.tsx b/pages/link/index.tsx index ac7d3b5..cfdc24e 100644 --- a/pages/link/index.tsx +++ b/pages/link/index.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { GetServerSidePropsContext } from "next"; import { useRouter } from "next/router"; import { parse } from "cookie"; @@ -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,7 @@ 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 { @@ -36,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: { @@ -68,17 +80,18 @@ 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); + + useEffect(() => { + setLinkCardList(initialLinkList, initialTotalCount); + }, [initialLinkList, initialTotalCount, setLinkCardList]); // 링크페이지의 query가 바뀌면 새로운 리스트로 업데이트 해주는 훅 - const loading = useFetchLinks( - setLinkCardList, - setTotalCount, - router.query, - router.pathname - ); + useFetchLinks(setLinkCardList, setIsLoading); return ( <> @@ -94,29 +107,27 @@ const LinkPage = ({ {!isMobile && }
-

유용한 글

{folder && ( - + <> +

{folderName as string}

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