-
- {isLoading ? (
-
- {Array.from({ length: 4 }).map((_, i) => (
-
- ))}
-
- ) : homePosts.length === 0 ? (
-
추천 글이 없습니다.
- ) : (
-
- {homePosts.map((post) => (
-
- ))}
-
- )}
-
+
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ ) : homePosts.length === 0 ? (
+
추천 글이 없습니다.
+ ) : (
+
+ {homePosts.map((post) => (
+
+ ))}
+
+ )}
+
-
-
- {isLoading ? (
-
- {Array.from({ length: 4 }).map((_, i) => (
-
- ))}
-
- ) : videos.length === 0 ? (
-
추천 영상이 없습니다.
- ) : (
-
- {videos.map((video) => (
-
- ))}
-
- )}
-
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ ) : videos.length === 0 ? (
+
+ 추천 영상이 없습니다.
+
+ ) : (
+
+ {videos.map((video) => (
+
+ ))}
+
+ )}
+
-
-
- {isLoading ? (
-
- {Array.from({ length: 4 }).map((_, i) => (
-
- ))}
-
- ) : books.length === 0 ? (
-
추천 서적이 없습니다.
- ) : (
-
- {books.map((book) => (
-
- ))}
-
- )}
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ ) : books.length === 0 ? (
+
+ 추천 서적이 없습니다.
+
+ ) : (
+
+ {books.map((book) => (
+
+ ))}
+
+ )}
+
);
diff --git a/components/features/my-page/recommend/RecommendedVideoList.tsx b/components/features/my-page/recommend/RecommendedVideoList.tsx
new file mode 100644
index 0000000..6aeb714
--- /dev/null
+++ b/components/features/my-page/recommend/RecommendedVideoList.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Skeleton } from "@/components/ui/skeleton";
+import { RecommendedVideoListItem } from "./RecommendedVideoListItem";
+import { fetchRecommendVideos } from "@/lib/mock/my-page-recommend-video";
+import type { MyPageRecommendVideo } from "@/types/myPage";
+
+function ListItemSkeleton() {
+ return (
+
+ );
+}
+
+export function RecommendedVideoList() {
+ const [videos, setVideos] = useState
([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isError, setIsError] = useState(false);
+
+ useEffect(() => {
+ fetchRecommendVideos()
+ .then((data) => setVideos(data))
+ .catch(() => setIsError(true))
+ .finally(() => setIsLoading(false));
+ }, []);
+
+ if (isError) {
+ return (
+
+ 불러오는 중 오류가 발생했습니다.
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ );
+ }
+
+ if (videos.length === 0) {
+ return (
+
+ 추천 영상이 없습니다.
+
+ );
+ }
+
+ return (
+
+ {videos.map((video) => (
+
+ ))}
+
+ );
+}
diff --git a/components/features/my-page/recommend/RecommendedVideoListItem.tsx b/components/features/my-page/recommend/RecommendedVideoListItem.tsx
new file mode 100644
index 0000000..dc4782b
--- /dev/null
+++ b/components/features/my-page/recommend/RecommendedVideoListItem.tsx
@@ -0,0 +1,53 @@
+import Image from "next/image";
+import { Youtube } from "lucide-react";
+import { formatDate } from "@/lib/utils";
+import type { MyPageRecommendVideo } from "@/types/myPage";
+
+function formatViews(views: number): string {
+ if (views >= 10000) return `${Math.floor(views / 10000)}만`;
+ if (views >= 1000) return `${Math.floor(views / 1000)}천`;
+ return String(views);
+}
+
+export function RecommendedVideoListItem({
+ video,
+}: {
+ video: MyPageRecommendVideo;
+}) {
+ const { title, channelName, thumbnail, url, duration, views, uploadedAt } =
+ video;
+
+ return (
+
+
+ {thumbnail ? (
+
+ ) : (
+
+
+
+ )}
+
+ {duration}
+
+
+
+
+
+ {title}
+
+
{channelName}
+
+ 조회 {formatViews(views)}
+ ·
+ {formatDate(uploadedAt)}
+
+
+
+ );
+}
diff --git a/components/features/my-page/scraps/ScrappedPostListItem.tsx b/components/features/my-page/scraps/ScrappedPostListItem.tsx
index 0e7bc20..37a236a 100644
--- a/components/features/my-page/scraps/ScrappedPostListItem.tsx
+++ b/components/features/my-page/scraps/ScrappedPostListItem.tsx
@@ -13,7 +13,7 @@ export function ScrappedPostListItem({ scrap }: { scrap: MyPageScrap }) {
return (
{thumbnail ? (
diff --git a/lib/mock/my-page-recommend-book.ts b/lib/mock/my-page-recommend-book.ts
index b63e4ad..97d6afe 100644
--- a/lib/mock/my-page-recommend-book.ts
+++ b/lib/mock/my-page-recommend-book.ts
@@ -5,8 +5,11 @@ export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [
bookId: "book-001",
title: "클린 아키텍처",
authors: ["로버트 C. 마틴"],
+ description:
+ "소프트웨어 구조 설계의 핵심 원칙과 클린 아키텍처의 개념을 설명하는 책.",
cover: "https://picsum.photos/seed/book1/300/400",
url: "https://www.yes24.com/Product/Goods/77283734",
+ price: 16000,
publisher: "인사이트",
publishedAt: "2019-08-20",
},
@@ -14,8 +17,11 @@ export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [
bookId: "book-002",
title: "가상 면접 사례로 배우는 대규모 시스템 설계 기초",
authors: ["알렉스 쉬"],
+ description:
+ "대규모 시스템 설계 면접을 대비하기 위한 핵심 개념과 설계 방법을 다룬다.",
cover: "https://picsum.photos/seed/book2/300/400",
url: "https://www.yes24.com/Product/Goods/102819435",
+ price: 21000,
publisher: "인사이트",
publishedAt: "2021-11-24",
},
@@ -23,8 +29,11 @@ export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [
bookId: "book-003",
title: "이펙티브 타입스크립트",
authors: ["댄 밴더캄"],
+ description:
+ "타입스크립트를 더 안전하고 효과적으로 사용하는 방법을 소개하는 실전 가이드.",
cover: null,
url: "https://www.yes24.com/Product/Goods/102124327",
+ price: 32000,
publisher: "인사이트",
publishedAt: "2021-11-12",
},
@@ -32,8 +41,11 @@ export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [
bookId: "book-004",
title: "데이터 중심 애플리케이션 설계",
authors: ["마틴 클레퍼만"],
+ description:
+ "데이터 시스템의 설계 원칙과 분산 시스템의 핵심 개념을 깊이 있게 설명한다.",
cover: "https://picsum.photos/seed/book4/300/400",
url: "https://www.yes24.com/Product/Goods/59566585",
+ price: 28000,
publisher: "위키북스",
publishedAt: "2018-12-04",
},
diff --git a/types/myPage.ts b/types/myPage.ts
index b33b804..72aa059 100644
--- a/types/myPage.ts
+++ b/types/myPage.ts
@@ -47,8 +47,10 @@ export interface MyPageRecommendBook {
bookId: string;
title: string;
authors: string[];
+ description?: string;
cover: string | null;
url: string;
+ price?: number;
publisher: string;
publishedAt: string;
}