diff --git a/components/Link/LinkCard.tsx b/components/Link/LinkCard.tsx
index 2b452ea..31abdf7 100644
--- a/components/Link/LinkCard.tsx
+++ b/components/Link/LinkCard.tsx
@@ -1,5 +1,7 @@
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";
@@ -15,14 +17,13 @@ interface LinkCardProps {
url: string;
createdAt: string;
};
- openEdit?: () => void;
- openDelete?: () => void;
}
-const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => {
- const [isSubscribed, setIsSubscribed] = useState(false);
+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, ".");
const createdTime = timeAgo(info.createdAt);
@@ -32,20 +33,39 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => {
// 모달이 열릴 때 드롭다운 닫기
useEffect(() => {
- if (isModalOpen) setIsDropdownOpen(false);
- }, [isModalOpen]);
+ if (isOpen) setIsDropdownOpen(false);
+ }, [isOpen]);
+
+ // 즐겨찾기 버튼 클릭 시 호출되는 함수
+ const handleFavoriteToggle = async () => {
+ setIsSubscribed((prev) => !prev);
+ try {
+ await putLinkFavorite(info.id, { favorite: !isSubscribed });
+ updateFavorite(info.id, !isSubscribed);
+ } catch (error) {
+ console.error("즐겨찾기 설정 중 오류 발생:", error);
+ }
+ };
// 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),
},
];
@@ -61,7 +81,7 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => {
{/* 즐겨찾기 페이지가 아닐 때에는 즐겨찾기 버튼 렌더링x */}
{!isFavoritePage && (
setIsSubscribed(!isSubscribed)}
+ onClick={handleFavoriteToggle}
className="absolute top-[15px] right-[15px] z-1"
>
{
};
// 링크의 즐겨찾기 설정(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);
+ 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 88%
rename from pages/api/links/[linkId].tsx
rename to pages/api/links/[linkId]/index.ts
index ae79ddc..403ea34 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) {
@@ -53,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 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}
- />
+
))}
diff --git a/store/useLinkCardStore.tsx b/store/useLinkCardStore.tsx
index 40b095b..ef5b92f 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";
import { LinkData } from "@/types/linkTypes";
@@ -11,6 +16,7 @@ interface LinkCardStore {
setLinkCardList: (list: LinkData[]) => void;
updateLink: (linkId: number, body: UpdateLinkBody) => Promise;
deleteLink: (linkId: number) => Promise;
+ updateFavorite: (linkId: number, favorite: boolean) => Promise;
}
export const useLinkCardStore = create((set) => ({
@@ -23,13 +29,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);
}
@@ -39,13 +50,33 @@ 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);
}
},
+
+ // 즐겨찾기 상태 업데이트
+ updateFavorite: async (linkId: number, favorite: boolean) => {
+ try {
+ await putLinkFavorite(linkId, { favorite });
+
+ // 변경된 항목만 상태 업데이트
+ set((state) => {
+ const updatedList = state.linkCardList.map((link) =>
+ link.id === linkId ? { ...link, favorite } : link
+ );
+ return { linkCardList: updatedList };
+ });
+ } catch (error) {
+ console.error("즐겨찾기 상태 업데이트 중 오류 발생:", error);
+ }
+ },
}));