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
40 changes: 30 additions & 10 deletions components/Link/LinkCard.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 토스트 적용하는게 좋을까요? (즐겨찾기 추가/실패)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분보다는 드롭다운에 수정하기, 삭제하기 했을 때 토스팅 적용이 더 나을 것 같습니다!

}
};

// 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),
},
];

Expand All @@ -61,7 +81,7 @@ const LinkCard = ({ openEdit, openDelete, info }: LinkCardProps) => {
{/* 즐겨찾기 페이지가 아닐 때에는 즐겨찾기 버튼 렌더링x */}
{!isFavoritePage && (
<div
Copy link
Collaborator

@junjeeong junjeeong Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

케밥 버튼과 즐겨찾기 버튼이 isFavoritePage이냐 아니냐에 따라서 조건부 렌더링이 되니
컴포넌트로 따로 만들어도 좋을 것 같다는 생각이 드네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 리팩토링 때 진행해보겠습니다~!

onClick={() => setIsSubscribed(!isSubscribed)}
onClick={handleFavoriteToggle}
className="absolute top-[15px] right-[15px] z-1"
>
<Image
Expand Down
8 changes: 4 additions & 4 deletions lib/api/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface putLinkURLProps {
url: string;
}

interface putFolderNameProps {
interface putLinkFavoriteProps {
favorite: boolean;
}

Expand Down Expand Up @@ -79,12 +79,12 @@ 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);
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);
Expand Down
51 changes: 51 additions & 0 deletions pages/api/links/[linkId]/favorite.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 11 additions & 6 deletions pages/api/links/[linkId].tsx → pages/api/links/[linkId]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
return res.status(500).json({ message: "서버 오류" });
}

// 링크 수정
case "PUT":
if (!linkId) {
Expand All @@ -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;
Expand Down
19 changes: 2 additions & 17 deletions pages/link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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가 바뀌면 새로운 리스트로 업데이트 해주는 훅
Expand All @@ -64,14 +64,6 @@ const LinkPage = ({
setLinkCardList(initialLinkList);
}, [initialLinkList, setLinkCardList]);

const handleModalOpen = (
type: "EditLink" | "DeleteLinkModal",
link: string,
linkId: number
) => {
openModal(type, { link, linkId });
};

return (
<>
<div className="bg-gray100 w-full h-[219px] flex justify-center items-center">
Expand All @@ -91,14 +83,7 @@ const LinkPage = ({
</div>
<CardsLayout>
{linkCardList.map((link) => (
<LinkCard
Copy link
Collaborator

@junjeeong junjeeong Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트가 많이 가벼워진게 보기 좋네요!

key={link.id}
openEdit={() => handleModalOpen("EditLink", link.url, link.id)}
openDelete={() =>
handleModalOpen("DeleteLinkModal", link.url, link.id)
}
info={link}
/>
<LinkCard key={link.id} info={link} />
))}
</CardsLayout>
<Pagination totalCount={totalCount} />
Expand Down
53 changes: 42 additions & 11 deletions store/useLinkCardStore.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -11,6 +16,7 @@ interface LinkCardStore {
setLinkCardList: (list: LinkData[]) => void;
updateLink: (linkId: number, body: UpdateLinkBody) => Promise<void>;
deleteLink: (linkId: number) => Promise<void>;
updateFavorite: (linkId: number, favorite: boolean) => Promise<void>;
}

export const useLinkCardStore = create<LinkCardStore>((set) => ({
Expand All @@ -23,13 +29,18 @@ export const useLinkCardStore = create<LinkCardStore>((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);
}
Expand All @@ -39,13 +50,33 @@ export const useLinkCardStore = create<LinkCardStore>((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);
}
},
}));
Loading