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
17 changes: 17 additions & 0 deletions components/Favorite/EmptyFavoriteList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

const EmptyFavoriteList = () => {
return (
<div className="flex flex-col justify-center items-center h-full p-10 bg-gray100 text-center text-gray600">
<div className="text-2xl md:text-3xl font-semibold text-gray600">
<span className="block mb-4">⭐️</span>
즐겨찾기 항목이 없습니다.
</div>
<div className="text-sm text-purple100 mt-2">
저장한 즐겨찾기 항목을 추가해보세요.
</div>
</div>
);
};

export default EmptyFavoriteList;
2 changes: 1 addition & 1 deletion components/Link/LinkCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import { useLinkCardStore } from "@/store/useLinkCardStore";
import { ensureAbsoluteUrl } from "@/lib/utils";
import { ensureAbsoluteUrl } from "@/util/ensureAbsoluteUrl";
import timeAgo from "@/util/timeAgo";
import Image from "next/image";
import Dropdown from "../Dropdown";
Expand Down
2 changes: 1 addition & 1 deletion components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Pagination: React.FC<PaginationProps> = ({ totalCount }) => {

const { page, pageSize } = router.query;
const currentPage = Number(page) || 1;
const currentPageSize = Number(pageSize) || 6;
const currentPageSize = Number(pageSize) || 9;
const totalPages = Math.ceil(totalCount / currentPageSize);

const [maxPagesToShow, setMaxPagesToShow] = useState(2);
Expand Down
14 changes: 11 additions & 3 deletions hooks/useFetchLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { useEffect } from "react";
import { useState, useEffect } from "react";
import { proxy } from "@/lib/api/axiosInstanceApi";
import { LinkData } from "@/types/linkTypes";
import { ParsedUrlQuery } from "querystring";
import useViewport from "./useViewport";

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

useEffect(() => {
const fetchLinks = async () => {
if (isTablet === undefined) return; // isTablet이 정의되지 않았으면 API 호출을 막음

setLoading(true); // API 호출 시작 시 로딩 상태 true

// 경로에 따라 API 엔드포인트 분기
let endpoint =
pathname === "/favorite"
Expand All @@ -26,7 +31,7 @@ const useFetchLinks = (
const res = await proxy.get(endpoint, {
params: {
page: query?.page,
pageSize: isTablet ? 6 : 10,
pageSize: isTablet ? 6 : 9,
search: query?.search,
},
});
Expand All @@ -35,8 +40,11 @@ const useFetchLinks = (
setTotalCount && setTotalCount(res.data.totalCount);
}
};

if (query) fetchLinks();
}, [setLinkCardList, query, isTablet]);

return loading; // 로딩 상태 반환
};

export default useFetchLinks;
14 changes: 11 additions & 3 deletions hooks/useViewport.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect } from "react";
import debounce from "lodash.debounce";

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

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

useEffect(() => {
handleResize();

window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);

return () => {
// 컴포넌트 언마운트 시 이벤트 리스너 제거 및 디바운스 정리
window.removeEventListener("resize", handleResize);
handleResize.cancel(); // debounce 취소
};
}, []);

const isPC = width >= breakpoints.PC.min;
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"axios": "^1.7.7",
"cookie": "^1.0.1",
"jwt-decode": "^4.0.0",
"lodash.debounce": "^4.0.8",
"next": "15.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -21,6 +22,7 @@
"zustand": "^5.0.1"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
2 changes: 1 addition & 1 deletion pages/api/favorites/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import axiosInstance from "@/lib/api/axiosInstanceApi";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const cookies = parse(req.headers.cookie || "");
const accessToken = cookies.accessToken;
const { page, pageSize = 6 } = req.query;
const { page, pageSize } = req.query;

if (!accessToken) {
return res.status(401).json({ message: "인증 오류: 토큰이 없습니다." });
Expand Down
2 changes: 1 addition & 1 deletion pages/api/links/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import axiosInstance from "@/lib/api/axiosInstanceApi";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const cookies = parse(req.headers.cookie || "");
const accessToken = cookies.accessToken;
const { page, pageSize = 6, search } = req.query;
const { page, pageSize, search } = req.query;

switch (req.method) {
case "GET":
Expand Down
72 changes: 48 additions & 24 deletions pages/favorite/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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";

interface FavoriteDataType {
id: number;
Expand All @@ -31,11 +33,20 @@ export const getServerSideProps: GetServerSideProps = async (
const { req } = context;
const cookies = parse(req.headers.cookie || "");
const accessToken = cookies.accessToken;

try {
const res = await axiosInstance.get("/favorites?page=1&pageSize=10", {
if (!accessToken) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

const res = await axiosInstance.get("/favorites", {
headers: { Authorization: `Bearer ${accessToken}` },
});

const { list, totalCount } = res.data || { list: [], totalCount: 0 };
return { props: { favoriteList: list, totalCount } };
} catch (error) {
Expand All @@ -44,42 +55,55 @@ export const getServerSideProps: GetServerSideProps = async (
}
};

const FavoritePage = ({ favoriteList, totalCount }: FavoriteProps) => {
const FavoritePage = ({
favoriteList,
totalCount: initialTotalCount,
}: FavoriteProps) => {
const router = useRouter();

const [linkCardList, setLinkCardList] =
useState<FavoriteDataType[]>(favoriteList);
const [totalCount, setTotalCount] = useState(initialTotalCount);

const loading = useFetchLinks(setLinkCardList, setTotalCount, router.query);

// 마이링크 페이지로 돌아감
const returnButton = () => {
router.push(`/link`);
};

useFetchLinks(setLinkCardList);
return (
<>
<div className="page-title pt-[10px] md:pt-5 pb-10 md:pb-[60px] bg-gray100 text-center">
<div className="flex justify-center items-center sm:h-[117px] h-[219px] sm:mb-5 mb-10 bg-gray100 text-center">
<h2 className="text-[32px] md:text-[40px] font-semibold">
⭐️ 즐겨찾기
</h2>
</div>
<Container>
<CardsLayout>
{linkCardList.length > 0
? linkCardList.map((favorite) => (
<LinkCard key={favorite.id} info={favorite} />
))
: null}
</CardsLayout>
<div className="flex justify-end">
<button onClick={returnButton} className="mb-5 text-purple100">
👈 마이링크로 돌아가기
</button>
</div>

{/* 즐겨찾기 항목이 없을 때 보여줄 메시지 (공통 컴포넌트로 사용할 건지 논의 필요) */}
{favoriteList.length === 0 && (
<div className="flex flex-col justify-center items-center h-full p-10 bg-gray100 text-center text-gray600">
<div className="text-2xl md:text-3xl font-semibold text-gray600">
<span className="block mb-4">⭐️</span>
즐겨찾기 항목이 없습니다.
</div>
<div className="text-sm text-purple100 mt-2">
저장한 즐겨찾기 항목을 추가해보세요.
</div>
{/* 로딩 중일 때 */}
{loading ? (
<div className="text-center">
<LoadingSpinner />
</div>
) : linkCardList.length > 0 ? (
<>
<CardsLayout>
{linkCardList.length > 0
? linkCardList.map((favorite) => (
<LinkCard key={favorite.id} info={favorite} />
))
: null}
</CardsLayout>
<Pagination totalCount={totalCount} />
</>
) : (
<EmptyFavoriteList />
)}
<Pagination totalCount={totalCount} />
</Container>
</>
);
Expand Down
16 changes: 14 additions & 2 deletions pages/link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import LinkCard from "@/components/Link/LinkCard";
import RenderEmptyLinkMessage from "@/components/Link/RenderEmptyLinkMessage";
import useFetchLinks from "@/hooks/useFetchLinks";
import useViewport from "@/hooks/useViewport";
import LoadingSpinner from "@/components/LoadingSpinner";

interface LinkPageProps {
linkList: LinkData[];
Expand Down Expand Up @@ -72,7 +73,12 @@ const LinkPage = ({
const [totalCount, setTotalCount] = useState(initialTotalCount);

// 링크페이지의 query가 바뀌면 새로운 리스트로 업데이트 해주는 훅
useFetchLinks(setLinkCardList, setTotalCount, router.query, router.pathname);
const loading = useFetchLinks(
setLinkCardList,
setTotalCount,
router.query,
router.pathname
);

return (
<>
Expand All @@ -97,7 +103,13 @@ const LinkPage = ({
/>
)}
</div>
{linkCardList ? (

{/* 로딩 중일 때 */}
{loading ? (
<div className="text-center">
<LoadingSpinner />
</div>
) : linkCardList.length > 0 ? (
<>
<CardsLayout>
{linkCardList.map((link) => (
Expand Down
File renamed without changes.
Loading