From a1d01dc59ac167df80503a9b176242793c69b3fd Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 14:10:58 +0900 Subject: [PATCH 1/7] =?UTF-8?q?Fix:=20putFolderName=20=EC=97=90=EC=84=9C?= =?UTF-8?q?=20putLinkFavorite=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/link.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/api/link.ts b/lib/api/link.ts index 54a62d5..a71bcfd 100644 --- a/lib/api/link.ts +++ b/lib/api/link.ts @@ -9,7 +9,7 @@ interface putLinkURLProps { url: string; } -interface putFolderNameProps { +interface putLinkFavoriteProps { favorite: boolean; } @@ -79,9 +79,9 @@ export const deleteLinkURL = async (linkId: number) => { }; // 링크의 즐겨찾기 설정(auth) -export const putFolderName = async ( +export const putLinkFavorite = async ( linkId: number, - body: putFolderNameProps + body: putLinkFavoriteProps ) => { try { const res = await proxy.put(`/api/links/${linkId}`, body); From 18ae88246040dd97d5b9b1426c8bc41b89c18374 Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 14:32:23 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Fix=20:=20api/links=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/LinkCard.tsx | 15 +++++- lib/api/link.ts | 2 +- pages/api/links/[linkId]/favorite.ts | 51 +++++++++++++++++++ .../links/{[linkId].tsx => [linkId]/index.ts} | 1 + 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 pages/api/links/[linkId]/favorite.ts rename pages/api/links/{[linkId].tsx => [linkId]/index.ts} (99%) diff --git a/components/LinkCard.tsx b/components/LinkCard.tsx index ee74346..b4b638e 100644 --- a/components/LinkCard.tsx +++ b/components/LinkCard.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import { putLinkFavorite } from "@/lib/api/link"; import timeAgo from "@/util/timAgo"; import Image from "next/image"; import Dropdown from "./Dropdown"; @@ -20,7 +21,7 @@ interface LinkCardProps { } const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { - const [isSubscribed, setIsSubscribed] = useState(false); + const [isSubscribed, setIsSubscribed] = useState(info.favorite || false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { isOpen: isModalOpen } = useModalStore(); @@ -35,6 +36,16 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { if (isModalOpen) setIsDropdownOpen(false); }, [isModalOpen]); + // 즐겨찾기 버튼 클릭 시 호출되는 함수 + const handleFavoriteToggle = async () => { + setIsSubscribed((prev) => !prev); + try { + await putLinkFavorite(info.id, { favorite: !isSubscribed }); + } catch (error) { + console.error("즐겨찾기 설정 중 오류 발생:", error); + } + }; + // dropdown 버튼 const toggleDropdown = () => setIsDropdownOpen((prev) => !prev); @@ -61,7 +72,7 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { {/* isFavoritePage일 때만 즐겨찾기 버튼 렌더링 */} {!isFavoritePage && (
setIsSubscribed(!isSubscribed)} + onClick={handleFavoriteToggle} className="absolute top-[15px] right-[15px] z-1" > { try { - const res = await proxy.put(`/api/links/${linkId}`, body); + const res = await proxy.put(`/api/links/${linkId}/favorite`, body); if (res.status >= 200 && res.status < 300) return res.data; } catch (err) { console.error("에러 메시지: ", err instanceof Error ? err.message : err); diff --git a/pages/api/links/[linkId]/favorite.ts b/pages/api/links/[linkId]/favorite.ts new file mode 100644 index 0000000..7dc0322 --- /dev/null +++ b/pages/api/links/[linkId]/favorite.ts @@ -0,0 +1,51 @@ +import axiosInstance from "@/lib/api/axiosInstanceApi"; +import { NextApiRequest, NextApiResponse } from "next"; +import { isAxiosError } from "axios"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + const token = req.cookies.accessToken; + + if (!token) { + return res.status(401).json({ error: "사용자 정보를 찾을 수 없습니다." }); + } + + const { linkId } = req.query; + + // 링크 즐겨 찾기 + switch (req.method) { + case "PUT": + const { favorite } = req.body; + if (favorite === undefined) { + return res.status(400).json({ message: "즐겨찾기 상태가 필요합니다." }); + } + + try { + await axiosInstance.put( + `/links/${linkId}/favorite`, + { favorite }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + return res.status(200).json({ + message: `링크 즐겨찾기 ${favorite ? "추가" : "삭제"} 성공`, + }); + } catch (error) { + if (isAxiosError(error) && error.response) { + const status = error.response.status; + const message = + error.response.data?.message || "알 수 없는 오류 발생"; + return res.status(status).json({ message }); + } + return res.status(500).json({ message: "서버 오류" }); + } + + default: + return res.status(405).json({ message: "허용되지 않은 접근 방법" }); + } +}; + +export default handler; diff --git a/pages/api/links/[linkId].tsx b/pages/api/links/[linkId]/index.ts similarity index 99% rename from pages/api/links/[linkId].tsx rename to pages/api/links/[linkId]/index.ts index ae79ddc..ced60d6 100644 --- a/pages/api/links/[linkId].tsx +++ b/pages/api/links/[linkId]/index.ts @@ -37,6 +37,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } return res.status(500).json({ message: "서버 오류" }); } + // 링크 수정 case "PUT": if (!linkId) { From e5b76ffab7c7f187bceb831d2e431fe3dba95fcf Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 20:16:18 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Fix:=20store=EC=97=90=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EC=83=81=ED=83=9C=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/LinkCard.tsx | 3 +++ store/useLinkCardStore.tsx | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/components/LinkCard.tsx b/components/LinkCard.tsx index b4b638e..2d855ab 100644 --- a/components/LinkCard.tsx +++ b/components/LinkCard.tsx @@ -5,6 +5,7 @@ import timeAgo from "@/util/timAgo"; import Image from "next/image"; import Dropdown from "./Dropdown"; import useModalStore from "@/store/useModalStore"; +import { useLinkCardStore } from "@/store/useLinkCardStore"; interface LinkCardProps { info: { @@ -24,6 +25,7 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { const [isSubscribed, setIsSubscribed] = useState(info.favorite || false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { isOpen: isModalOpen } = useModalStore(); + const { updateFavorite } = useLinkCardStore(); const formattedDate = info.createdAt?.slice(0, 10).replace(/-/g, "."); const createdTime = timeAgo(info.createdAt); @@ -41,6 +43,7 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { setIsSubscribed((prev) => !prev); try { await putLinkFavorite(info.id, { favorite: !isSubscribed }); + updateFavorite(info.id, !isSubscribed); } catch (error) { console.error("즐겨찾기 설정 중 오류 발생:", error); } diff --git a/store/useLinkCardStore.tsx b/store/useLinkCardStore.tsx index 6f9de85..3534d28 100644 --- a/store/useLinkCardStore.tsx +++ b/store/useLinkCardStore.tsx @@ -1,4 +1,9 @@ -import { deleteLinkURL, getLinks, putLinkURL } from "@/lib/api/link"; +import { + deleteLinkURL, + getLinks, + putLinkFavorite, + putLinkURL, +} from "@/lib/api/link"; import { create } from "zustand"; interface LinkCardDataType { @@ -20,6 +25,7 @@ interface LinkCardStore { setLinkCardList: (list: LinkCardDataType[]) => void; updateLink: (linkId: number, body: UpdateLinkBody) => Promise; deleteLink: (linkId: number) => Promise; + updateFavorite: (linkId: number, favorite: boolean) => Promise; } export const useLinkCardStore = create((set) => ({ @@ -57,4 +63,20 @@ export const useLinkCardStore = create((set) => ({ console.error("삭제 중 오류 발생:", error); } }, + + // 즐겨찾기 상태 업데이트 + updateFavorite: async (linkId: number, favorite: boolean) => { + try { + // API 호출하여 즐겨찾기 상태 업데이트 + await putLinkFavorite(linkId, { favorite }); + + // 링크 목록 새로 가져와서 상태 업데이트 + const res = await getLinks(); + const updatedList = res.list; + + set({ linkCardList: updatedList }); + } catch (error) { + console.error("즐겨찾기 상태 업데이트 중 오류 발생:", error); + } + }, })); From 4f7965429b62f309cea46b0a28aaf48c74b71101 Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 20:53:08 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Fix:=20store=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B3=80=EA=B2=BD=EB=90=9C=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=EB=A7=8C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/api/links/[linkId]/index.ts | 16 +++++++----- pages/link/index.tsx | 5 ++++ store/useLinkCardStore.tsx | 41 +++++++++++++++++++------------ 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/pages/api/links/[linkId]/index.ts b/pages/api/links/[linkId]/index.ts index ced60d6..403ea34 100644 --- a/pages/api/links/[linkId]/index.ts +++ b/pages/api/links/[linkId]/index.ts @@ -54,13 +54,17 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } try { - await axiosInstance.put(`/links/${linkId}`, updateData, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const updatedLink = await axiosInstance.put( + `/links/${linkId}`, + updateData, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); - return res.status(200).json({ message: "링크 업데이트 성공" }); + return res.status(200).json(updatedLink.data); } catch (error) { if (isAxiosError(error) && error.response) { const status = error.response.status; diff --git a/pages/link/index.tsx b/pages/link/index.tsx index 97afa7e..c4d014b 100644 --- a/pages/link/index.tsx +++ b/pages/link/index.tsx @@ -68,6 +68,11 @@ const LinkPage = ({ const { isOpen, openModal } = useModalStore(); const { linkCardList, setLinkCardList } = useLinkCardStore(); + useEffect(() => { + // 상태 변화 감지 후 바로 UI 업데이트 처리 + console.log("linkCardList updated:", linkCardList); + }, [linkCardList]); + // 클라이언트에서 초기 목록을 설정 useEffect(() => { setLinkCardList(linkList); diff --git a/store/useLinkCardStore.tsx b/store/useLinkCardStore.tsx index 3534d28..a5bee5a 100644 --- a/store/useLinkCardStore.tsx +++ b/store/useLinkCardStore.tsx @@ -38,13 +38,18 @@ export const useLinkCardStore = create((set) => ({ // 수정 요청 보낸 후 목록 가져오기 updateLink: async (linkId: number, body: UpdateLinkBody) => { try { - await putLinkURL(linkId, body); + // 프록시 서버에서 수정된 링크 데이터를 받아옴 + const updatedData = await putLinkURL(linkId, body); - const res = await getLinks(); - const updatedList = res.list; - - // 상태 업데이트 - set({ linkCardList: updatedList }); + if (updatedData) { + // 수정된 데이터를 사용하여 상태 업데이트 + set((state) => { + const updatedList = state.linkCardList.map((link) => + link.id === linkId ? { ...link, ...updatedData } : link + ); + return { linkCardList: updatedList }; + }); + } } catch (error) { console.error("삭제 중 오류 발생:", error); } @@ -54,11 +59,13 @@ export const useLinkCardStore = create((set) => ({ deleteLink: async (linkId: number) => { try { await deleteLinkURL(linkId); - const res = await getLinks(); - const updatedList = res.list; - - // 상태 업데이트 - set({ linkCardList: updatedList }); + // 삭제된 항목을 제외한 나머지 항목으로 상태 업데이트 + set((state) => { + const updatedList = state.linkCardList.filter( + (link) => link.id !== linkId + ); + return { linkCardList: updatedList }; + }); } catch (error) { console.error("삭제 중 오류 발생:", error); } @@ -70,11 +77,13 @@ export const useLinkCardStore = create((set) => ({ // API 호출하여 즐겨찾기 상태 업데이트 await putLinkFavorite(linkId, { favorite }); - // 링크 목록 새로 가져와서 상태 업데이트 - const res = await getLinks(); - const updatedList = res.list; - - set({ linkCardList: updatedList }); + // 변경된 항목만 상태 업데이트 + set((state) => { + const updatedList = state.linkCardList.map((link) => + link.id === linkId ? { ...link, favorite } : link + ); + return { linkCardList: updatedList }; + }); } catch (error) { console.error("즐겨찾기 상태 업데이트 중 오류 발생:", error); } From 1de5822bc6f80a02532e9e344e35a0f954fe2a21 Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 20:56:44 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/link/index.tsx | 5 ----- store/useLinkCardStore.tsx | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pages/link/index.tsx b/pages/link/index.tsx index c4d014b..97afa7e 100644 --- a/pages/link/index.tsx +++ b/pages/link/index.tsx @@ -68,11 +68,6 @@ const LinkPage = ({ const { isOpen, openModal } = useModalStore(); const { linkCardList, setLinkCardList } = useLinkCardStore(); - useEffect(() => { - // 상태 변화 감지 후 바로 UI 업데이트 처리 - console.log("linkCardList updated:", linkCardList); - }, [linkCardList]); - // 클라이언트에서 초기 목록을 설정 useEffect(() => { setLinkCardList(linkList); diff --git a/store/useLinkCardStore.tsx b/store/useLinkCardStore.tsx index a5bee5a..8fc7a23 100644 --- a/store/useLinkCardStore.tsx +++ b/store/useLinkCardStore.tsx @@ -41,8 +41,8 @@ export const useLinkCardStore = create((set) => ({ // 프록시 서버에서 수정된 링크 데이터를 받아옴 const updatedData = await putLinkURL(linkId, body); + // 수정된 데이터를 사용하여 상태 업데이트 if (updatedData) { - // 수정된 데이터를 사용하여 상태 업데이트 set((state) => { const updatedList = state.linkCardList.map((link) => link.id === linkId ? { ...link, ...updatedData } : link @@ -59,6 +59,7 @@ export const useLinkCardStore = create((set) => ({ deleteLink: async (linkId: number) => { try { await deleteLinkURL(linkId); + // 삭제된 항목을 제외한 나머지 항목으로 상태 업데이트 set((state) => { const updatedList = state.linkCardList.filter( @@ -74,7 +75,6 @@ export const useLinkCardStore = create((set) => ({ // 즐겨찾기 상태 업데이트 updateFavorite: async (linkId: number, favorite: boolean) => { try { - // API 호출하여 즐겨찾기 상태 업데이트 await putLinkFavorite(linkId, { favorite }); // 변경된 항목만 상태 업데이트 From 4ee35f6eeaa277572e875d3c4934888f4116b09b Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 21:03:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Chore:=20=EA=B8=B0=EC=A1=B4=20linkCard?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20=EB=82=B4=EC=97=AD=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Link/LinkCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Link/LinkCard.tsx b/components/Link/LinkCard.tsx index 42084c6..46ac3e9 100644 --- a/components/Link/LinkCard.tsx +++ b/components/Link/LinkCard.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { putLinkFavorite } from "@/lib/api/link"; +import { useLinkCardStore } from "@/store/useLinkCardStore"; import timeAgo from "@/util/timAgo"; import Image from "next/image"; import Dropdown from "../Dropdown"; import useModalStore from "@/store/useModalStore"; -import { useLinkCardStore } from "@/store/useLinkCardStore"; interface LinkCardProps { info: { From e5dd248e207c68e18b67bc4c5d35a8c679cd81e4 Mon Sep 17 00:00:00 2001 From: 99uminji Date: Thu, 14 Nov 2024 21:49:28 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Fix:=20handleModalOpen=20linkcard=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Link/LinkCard.tsx | 22 ++++++++++++++-------- pages/link/index.tsx | 19 ++----------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/components/Link/LinkCard.tsx b/components/Link/LinkCard.tsx index 46ac3e9..8222c94 100644 --- a/components/Link/LinkCard.tsx +++ b/components/Link/LinkCard.tsx @@ -17,14 +17,12 @@ interface LinkCardProps { url: string; createdAt: string; }; - openEdit?: () => void; - openDelete?: () => void; } -const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { +const LinkCard = ({ info }: LinkCardProps) => { const [isSubscribed, setIsSubscribed] = useState(info.favorite || false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const { isOpen: isModalOpen } = useModalStore(); + const { isOpen, openModal } = useModalStore(); const { updateFavorite } = useLinkCardStore(); const formattedDate = info.createdAt?.slice(0, 10).replace(/-/g, "."); @@ -35,8 +33,8 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { // 모달이 열릴 때 드롭다운 닫기 useEffect(() => { - if (isModalOpen) setIsDropdownOpen(false); - }, [isModalOpen]); + if (isOpen) setIsDropdownOpen(false); + }, [isOpen]); // 즐겨찾기 버튼 클릭 시 호출되는 함수 const handleFavoriteToggle = async () => { @@ -52,14 +50,22 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => { // dropdown 버튼 const toggleDropdown = () => setIsDropdownOpen((prev) => !prev); + const handleModalOpen = ( + type: "EditLink" | "DeleteLinkModal", + link: string, + linkId: number + ) => { + openModal(type, { link, linkId }); + }; + const dropdownItems = [ { label: "수정하기", - onClick: openEdit, + onClick: () => handleModalOpen("EditLink", info.url, info.id), }, { label: "삭제하기", - onClick: openDelete, + onClick: () => handleModalOpen("DeleteLinkModal", info.url, info.id), }, ]; diff --git a/pages/link/index.tsx b/pages/link/index.tsx index 65884e9..8f18dd9 100644 --- a/pages/link/index.tsx +++ b/pages/link/index.tsx @@ -52,8 +52,8 @@ const LinkPage = ({ }: LinkPageProps) => { const router = useRouter(); const { search } = router.query; - const { isOpen, openModal } = useModalStore(); const { linkCardList, setLinkCardList } = useLinkCardStore(); + const { isOpen } = useModalStore(); const [folderList, setFolderList] = useState(initialFolderList); // 링크페이지의 query가 바뀌면 새로운 리스트로 업데이트 해주는 훅 @@ -64,14 +64,6 @@ const LinkPage = ({ setLinkCardList(initialLinkList); }, [initialLinkList, setLinkCardList]); - const handleModalOpen = ( - type: "EditLink" | "DeleteLinkModal", - link: string, - linkId: number - ) => { - openModal(type, { link, linkId }); - }; - return ( <>
@@ -91,14 +83,7 @@ const LinkPage = ({
{linkCardList.map((link) => ( - handleModalOpen("EditLink", link.url, link.id)} - openDelete={() => - handleModalOpen("DeleteLinkModal", link.url, link.id) - } - info={link} - /> + ))}