Skip to content

Commit 53c43e3

Browse files
authored
Merge pull request #116 from codeit9-temporary/Refactor/favorite
Refactor: 즐겨찾기 페이지 리팩토링
2 parents de86de1 + e137f65 commit 53c43e3

File tree

12 files changed

+132
-36
lines changed

12 files changed

+132
-36
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from "react";
2+
3+
const EmptyFavoriteList = () => {
4+
return (
5+
<div className="flex flex-col justify-center items-center h-full p-10 bg-gray100 text-center text-gray600">
6+
<div className="text-2xl md:text-3xl font-semibold text-gray600">
7+
<span className="block mb-4">⭐️</span>
8+
즐겨찾기 항목이 없습니다.
9+
</div>
10+
<div className="text-sm text-purple100 mt-2">
11+
저장한 즐겨찾기 항목을 추가해보세요.
12+
</div>
13+
</div>
14+
);
15+
};
16+
17+
export default EmptyFavoriteList;

components/Link/LinkCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useRef, useState } from "react";
22
import { useRouter } from "next/router";
33
import { useLinkCardStore } from "@/store/useLinkCardStore";
4-
import { ensureAbsoluteUrl } from "@/lib/utils";
4+
import { ensureAbsoluteUrl } from "@/util/ensureAbsoluteUrl";
55
import timeAgo from "@/util/timeAgo";
66
import Image from "next/image";
77
import Dropdown from "../Dropdown";

components/Pagination.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const Pagination: React.FC<PaginationProps> = ({ totalCount }) => {
1616

1717
const { page, pageSize } = router.query;
1818
const currentPage = Number(page) || 1;
19-
const currentPageSize = Number(pageSize) || 6;
19+
const currentPageSize = Number(pageSize) || 9;
2020
const totalPages = Math.ceil(totalCount / currentPageSize);
2121

2222
const [maxPagesToShow, setMaxPagesToShow] = useState(2);

hooks/useFetchLinks.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
import { useEffect } from "react";
1+
import { useState, useEffect } from "react";
22
import { proxy } from "@/lib/api/axiosInstanceApi";
33
import { LinkData } from "@/types/linkTypes";
44
import { ParsedUrlQuery } from "querystring";
55
import useViewport from "./useViewport";
66

7-
// 링크페이지의 query가 바뀌면 그에 맞는 링크들을 보여주는 훅
7+
// 링크 페이지의 query가 바뀌면 그에 맞는 링크들을 보여주는 훅
88
const useFetchLinks = (
99
setLinkCardList: React.Dispatch<React.SetStateAction<LinkData[]>>,
1010
setTotalCount?: React.Dispatch<React.SetStateAction<number>>,
1111
query?: ParsedUrlQuery,
1212
pathname?: string
1313
) => {
1414
const { isTablet } = useViewport();
15+
const [loading, setLoading] = useState<boolean>(false); // 로딩 상태 관리
1516

1617
useEffect(() => {
1718
const fetchLinks = async () => {
19+
if (isTablet === undefined) return; // isTablet이 정의되지 않았으면 API 호출을 막음
20+
21+
setLoading(true); // API 호출 시작 시 로딩 상태 true
22+
1823
// 경로에 따라 API 엔드포인트 분기
1924
let endpoint =
2025
pathname === "/favorite"
@@ -26,7 +31,7 @@ const useFetchLinks = (
2631
const res = await proxy.get(endpoint, {
2732
params: {
2833
page: query?.page,
29-
pageSize: isTablet ? 6 : 10,
34+
pageSize: isTablet ? 6 : 9,
3035
search: query?.search,
3136
},
3237
});
@@ -35,8 +40,11 @@ const useFetchLinks = (
3540
setTotalCount && setTotalCount(res.data.totalCount);
3641
}
3742
};
43+
3844
if (query) fetchLinks();
3945
}, [setLinkCardList, query, isTablet]);
46+
47+
return loading; // 로딩 상태 반환
4048
};
4149

4250
export default useFetchLinks;

hooks/useViewport.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useEffect } from "react";
2+
import debounce from "lodash.debounce";
23

34
const breakpoints = {
45
PC: { min: 1200 },
@@ -10,14 +11,21 @@ const breakpoints = {
1011
function useViewport(initialWidth = 0) {
1112
const [width, setWidth] = useState(initialWidth);
1213

13-
const handleResize = () => {
14+
// debounce를 사용하여 resize 이벤트 핸들러 생성
15+
const handleResize = debounce(() => {
1416
setWidth(window.innerWidth);
15-
};
17+
}, 200); // 200ms 지연
1618

1719
useEffect(() => {
1820
handleResize();
21+
1922
window.addEventListener("resize", handleResize);
20-
return () => window.removeEventListener("resize", handleResize);
23+
24+
return () => {
25+
// 컴포넌트 언마운트 시 이벤트 리스너 제거 및 디바운스 정리
26+
window.removeEventListener("resize", handleResize);
27+
handleResize.cancel(); // debounce 취소
28+
};
2129
}, []);
2230

2331
const isPC = width >= breakpoints.PC.min;

package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"axios": "^1.7.7",
1313
"cookie": "^1.0.1",
1414
"jwt-decode": "^4.0.0",
15+
"lodash.debounce": "^4.0.8",
1516
"next": "15.0.2",
1617
"react": "^18.2.0",
1718
"react-dom": "^18.2.0",
@@ -21,6 +22,7 @@
2122
"zustand": "^5.0.1"
2223
},
2324
"devDependencies": {
25+
"@types/lodash.debounce": "^4.0.9",
2426
"@types/node": "^20",
2527
"@types/react": "^18",
2628
"@types/react-dom": "^18",

pages/api/favorites/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import axiosInstance from "@/lib/api/axiosInstanceApi";
55
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
66
const cookies = parse(req.headers.cookie || "");
77
const accessToken = cookies.accessToken;
8-
const { page, pageSize = 6 } = req.query;
8+
const { page, pageSize } = req.query;
99

1010
if (!accessToken) {
1111
return res.status(401).json({ message: "인증 오류: 토큰이 없습니다." });

pages/api/links/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import axiosInstance from "@/lib/api/axiosInstanceApi";
55
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
66
const cookies = parse(req.headers.cookie || "");
77
const accessToken = cookies.accessToken;
8-
const { page, pageSize = 6, search } = req.query;
8+
const { page, pageSize, search } = req.query;
99

1010
switch (req.method) {
1111
case "GET":

pages/favorite/index.tsx

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import useFetchLinks from "@/hooks/useFetchLinks";
88
import { useRouter } from "next/router";
99
import { useState } from "react";
1010
import { parse } from "cookie";
11+
import LoadingSpinner from "@/components/LoadingSpinner";
12+
import EmptyFavoriteList from "@/components/Favorite/EmptyFavoriteList";
1113

1214
interface FavoriteDataType {
1315
id: number;
@@ -31,11 +33,20 @@ export const getServerSideProps: GetServerSideProps = async (
3133
const { req } = context;
3234
const cookies = parse(req.headers.cookie || "");
3335
const accessToken = cookies.accessToken;
36+
3437
try {
35-
const res = await axiosInstance.get("/favorites?page=1&pageSize=10", {
38+
if (!accessToken) {
39+
return {
40+
redirect: {
41+
destination: "/login",
42+
permanent: false,
43+
},
44+
};
45+
}
46+
47+
const res = await axiosInstance.get("/favorites", {
3648
headers: { Authorization: `Bearer ${accessToken}` },
3749
});
38-
3950
const { list, totalCount } = res.data || { list: [], totalCount: 0 };
4051
return { props: { favoriteList: list, totalCount } };
4152
} catch (error) {
@@ -44,42 +55,55 @@ export const getServerSideProps: GetServerSideProps = async (
4455
}
4556
};
4657

47-
const FavoritePage = ({ favoriteList, totalCount }: FavoriteProps) => {
58+
const FavoritePage = ({
59+
favoriteList,
60+
totalCount: initialTotalCount,
61+
}: FavoriteProps) => {
4862
const router = useRouter();
49-
5063
const [linkCardList, setLinkCardList] =
5164
useState<FavoriteDataType[]>(favoriteList);
65+
const [totalCount, setTotalCount] = useState(initialTotalCount);
66+
67+
const loading = useFetchLinks(setLinkCardList, setTotalCount, router.query);
68+
69+
// 마이링크 페이지로 돌아감
70+
const returnButton = () => {
71+
router.push(`/link`);
72+
};
5273

53-
useFetchLinks(setLinkCardList);
5474
return (
5575
<>
56-
<div className="page-title pt-[10px] md:pt-5 pb-10 md:pb-[60px] bg-gray100 text-center">
76+
<div className="flex justify-center items-center sm:h-[117px] h-[219px] sm:mb-5 mb-10 bg-gray100 text-center">
5777
<h2 className="text-[32px] md:text-[40px] font-semibold">
5878
⭐️ 즐겨찾기
5979
</h2>
6080
</div>
6181
<Container>
62-
<CardsLayout>
63-
{linkCardList.length > 0
64-
? linkCardList.map((favorite) => (
65-
<LinkCard key={favorite.id} info={favorite} />
66-
))
67-
: null}
68-
</CardsLayout>
82+
<div className="flex justify-end">
83+
<button onClick={returnButton} className="mb-5 text-purple100">
84+
👈 마이링크로 돌아가기
85+
</button>
86+
</div>
6987

70-
{/* 즐겨찾기 항목이 없을 때 보여줄 메시지 (공통 컴포넌트로 사용할 건지 논의 필요) */}
71-
{favoriteList.length === 0 && (
72-
<div className="flex flex-col justify-center items-center h-full p-10 bg-gray100 text-center text-gray600">
73-
<div className="text-2xl md:text-3xl font-semibold text-gray600">
74-
<span className="block mb-4">⭐️</span>
75-
즐겨찾기 항목이 없습니다.
76-
</div>
77-
<div className="text-sm text-purple100 mt-2">
78-
저장한 즐겨찾기 항목을 추가해보세요.
79-
</div>
88+
{/* 로딩 중일 때 */}
89+
{loading ? (
90+
<div className="text-center">
91+
<LoadingSpinner />
8092
</div>
93+
) : linkCardList.length > 0 ? (
94+
<>
95+
<CardsLayout>
96+
{linkCardList.length > 0
97+
? linkCardList.map((favorite) => (
98+
<LinkCard key={favorite.id} info={favorite} />
99+
))
100+
: null}
101+
</CardsLayout>
102+
<Pagination totalCount={totalCount} />
103+
</>
104+
) : (
105+
<EmptyFavoriteList />
81106
)}
82-
<Pagination totalCount={totalCount} />
83107
</Container>
84108
</>
85109
);

0 commit comments

Comments
 (0)