@@ -37,7 +47,7 @@ export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
setSort(v as SortOrder)}
+ onValueChange={(v) => { setSort(v as SortOrder); setCurrentPage(1); }}
>
최신순
@@ -55,11 +65,19 @@ export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
틀린 퀴즈가 없습니다.
) : (
-
- {sorted.map((quiz) => (
-
- ))}
-
+ <>
+
+ {pagedItems.map((quiz) => (
+
+ ))}
+
+
+ >
)}
);
diff --git a/components/features/my-page/recommend/RecommendedBookList.tsx b/components/features/my-page/recommend/RecommendedBookList.tsx
index 8b2a069..7751103 100644
--- a/components/features/my-page/recommend/RecommendedBookList.tsx
+++ b/components/features/my-page/recommend/RecommendedBookList.tsx
@@ -1,11 +1,14 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useState, useMemo } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { RecommendedBookListItem } from "./RecommendedBookListItem";
+import { MyPagePagination } from "../MyPagePagination";
import { fetchRecommendBooks } from "@/lib/mock/my-page-recommend-book";
import type { MyPageRecommendBook } from "@/types/myPage";
+const PAGE_SIZE = 10;
+
function ListItemSkeleton() {
return (
@@ -40,6 +43,7 @@ export function RecommendedBookList() {
const [books, setBooks] = useState
([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
fetchRecommendBooks()
@@ -48,6 +52,12 @@ export function RecommendedBookList() {
.finally(() => setIsLoading(false));
}, []);
+ const totalPages = Math.ceil(books.length / PAGE_SIZE);
+ const pagedItems = useMemo(
+ () => books.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),
+ [books, currentPage],
+ );
+
if (isError) {
return (
@@ -75,10 +85,18 @@ export function RecommendedBookList() {
}
return (
-
- {books.map((book) => (
-
- ))}
-
+ <>
+
+ {pagedItems.map((book) => (
+
+ ))}
+
+
+ >
);
}
diff --git a/components/features/my-page/recommend/RecommendedHomePostList.tsx b/components/features/my-page/recommend/RecommendedHomePostList.tsx
index e473e11..26ff353 100644
--- a/components/features/my-page/recommend/RecommendedHomePostList.tsx
+++ b/components/features/my-page/recommend/RecommendedHomePostList.tsx
@@ -1,11 +1,14 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useState, useMemo } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { RecommendedHomePostListItem } from "./RecommendedHomePostListItem";
+import { MyPagePagination } from "../MyPagePagination";
import { fetchRecommendHomePosts } from "@/lib/mock/my-page-recommend-home";
import type { MyPageRecommendHomePost } from "@/types/myPage";
+const PAGE_SIZE = 10;
+
function ListItemSkeleton() {
return (
@@ -24,6 +27,7 @@ export function RecommendedHomePostList() {
const [posts, setPosts] = useState
([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
fetchRecommendHomePosts()
@@ -32,6 +36,12 @@ export function RecommendedHomePostList() {
.finally(() => setIsLoading(false));
}, []);
+ const totalPages = Math.ceil(posts.length / PAGE_SIZE);
+ const pagedItems = useMemo(
+ () => posts.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),
+ [posts, currentPage],
+ );
+
if (isError) {
return (
@@ -59,10 +69,18 @@ export function RecommendedHomePostList() {
}
return (
-
- {posts.map((post) => (
-
- ))}
-
+ <>
+
+ {pagedItems.map((post) => (
+
+ ))}
+
+
+ >
);
}
diff --git a/components/features/my-page/recommend/RecommendedVideoList.tsx b/components/features/my-page/recommend/RecommendedVideoList.tsx
index 6aeb714..645db31 100644
--- a/components/features/my-page/recommend/RecommendedVideoList.tsx
+++ b/components/features/my-page/recommend/RecommendedVideoList.tsx
@@ -1,11 +1,14 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useState, useMemo } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { RecommendedVideoListItem } from "./RecommendedVideoListItem";
+import { MyPagePagination } from "../MyPagePagination";
import { fetchRecommendVideos } from "@/lib/mock/my-page-recommend-video";
import type { MyPageRecommendVideo } from "@/types/myPage";
+const PAGE_SIZE = 10;
+
function ListItemSkeleton() {
return (
@@ -23,6 +26,7 @@ export function RecommendedVideoList() {
const [videos, setVideos] = useState
([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
fetchRecommendVideos()
@@ -31,6 +35,12 @@ export function RecommendedVideoList() {
.finally(() => setIsLoading(false));
}, []);
+ const totalPages = Math.ceil(videos.length / PAGE_SIZE);
+ const pagedItems = useMemo(
+ () => videos.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),
+ [videos, currentPage],
+ );
+
if (isError) {
return (
@@ -58,10 +68,18 @@ export function RecommendedVideoList() {
}
return (
-
- {videos.map((video) => (
-
- ))}
-
+ <>
+
+ {pagedItems.map((video) => (
+
+ ))}
+
+
+ >
);
}
diff --git a/components/features/my-page/scraps/ScrappedPostsList.tsx b/components/features/my-page/scraps/ScrappedPostsList.tsx
index f309ea9..7f65ce9 100644
--- a/components/features/my-page/scraps/ScrappedPostsList.tsx
+++ b/components/features/my-page/scraps/ScrappedPostsList.tsx
@@ -11,9 +11,12 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrappedPostListItem } from "./ScrappedPostListItem";
+import { MyPagePagination } from "../MyPagePagination";
import { fetchMyScraps } from "@/lib/mock/my-page-scraps";
import type { MyPageScrap } from "@/types/myPage";
+const PAGE_SIZE = 10;
+
type SortOrder = "newest" | "oldest";
function ListItemSkeleton() {
@@ -36,6 +39,7 @@ export function ScrappedPostsList() {
const [isError, setIsError] = useState(false);
const [query, setQuery] = useState("");
const [sort, setSort] = useState("newest");
+ const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
fetchMyScraps()
@@ -62,6 +66,12 @@ export function ScrappedPostsList() {
});
}, [scraps, query, sort]);
+ const totalPages = Math.ceil(processed.length / PAGE_SIZE);
+ const pagedItems = processed.slice(
+ (currentPage - 1) * PAGE_SIZE,
+ currentPage * PAGE_SIZE,
+ );
+
if (isLoading) {
return (
@@ -93,7 +103,7 @@ export function ScrappedPostsList() {
type="text"
placeholder="검색"
value={query}
- onChange={(e) => setQuery(e.target.value)}
+ onChange={(e) => { setQuery(e.target.value); setCurrentPage(1); }}
className="h-8 w-40 rounded-md border border-border bg-background pl-7 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
/>
@@ -105,7 +115,7 @@ export function ScrappedPostsList() {
setSort(v as SortOrder)}
+ onValueChange={(v) => { setSort(v as SortOrder); setCurrentPage(1); }}
>
최신순
@@ -124,11 +134,19 @@ export function ScrappedPostsList() {
{query ? "검색 결과가 없습니다." : "스크랩한 글이 없습니다."}
) : (
-
- {processed.map((scrap) => (
-
- ))}
-
+ <>
+
+ {pagedItems.map((scrap) => (
+
+ ))}
+
+
+ >
)}
);
diff --git a/lib/mock/my-page-recommend-book.ts b/lib/mock/my-page-recommend-book.ts
index 97d6afe..cb07514 100644
--- a/lib/mock/my-page-recommend-book.ts
+++ b/lib/mock/my-page-recommend-book.ts
@@ -49,11 +49,108 @@ export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [
publisher: "위키북스",
publishedAt: "2018-12-04",
},
+ {
+ bookId: "book-005",
+ title: "리팩터링 2판",
+ authors: ["마틴 파울러"],
+ description:
+ "코드를 더 깔끔하게 만드는 리팩터링 기법을 JavaScript 예제와 함께 설명한다.",
+ cover: "https://picsum.photos/seed/book5/300/400",
+ url: "https://www.yes24.com/Product/Goods/89649360",
+ price: 38000,
+ publisher: "한빛미디어",
+ publishedAt: "2020-04-01",
+ },
+ {
+ bookId: "book-006",
+ title: "HTTP 완벽 가이드",
+ authors: ["데이빗 고울리", "브라이언 토티"],
+ description:
+ "웹의 근간이 되는 HTTP 프로토콜의 동작 원리와 최신 스펙을 상세히 다룬다.",
+ cover: null,
+ url: "https://www.yes24.com/Product/Goods/15381085",
+ price: 49000,
+ publisher: "인사이트",
+ publishedAt: "2014-12-15",
+ },
+ {
+ bookId: "book-007",
+ title: "자바스크립트 딥 다이브",
+ authors: ["이웅모"],
+ description:
+ "자바스크립트의 동작 원리를 기초부터 깊이 있게 이해할 수 있는 국내 저술서.",
+ cover: "https://picsum.photos/seed/book7/300/400",
+ url: "https://www.yes24.com/Product/Goods/92742567",
+ price: 45000,
+ publisher: "위키북스",
+ publishedAt: "2020-09-25",
+ },
+ {
+ bookId: "book-008",
+ title: "쏙쏙 들어오는 함수형 코딩",
+ authors: ["에릭 노먼드"],
+ description:
+ "함수형 프로그래밍 패러다임을 실용적인 관점에서 쉽게 설명하는 입문서.",
+ cover: "https://picsum.photos/seed/book8/300/400",
+ url: "https://www.yes24.com/Product/Goods/108748841",
+ price: 33000,
+ publisher: "제이펍",
+ publishedAt: "2022-11-30",
+ },
+ {
+ bookId: "book-009",
+ title: "실용주의 프로그래머",
+ authors: ["데이비드 토머스", "앤드류 헌트"],
+ description:
+ "더 나은 개발자가 되기 위한 실질적인 조언을 담은 개발 철학서의 고전.",
+ cover: null,
+ url: "https://www.yes24.com/Product/Goods/107077663",
+ price: 35000,
+ publisher: "인사이트",
+ publishedAt: "2022-02-18",
+ },
+ {
+ bookId: "book-010",
+ title: "그림으로 배우는 네트워크 원리",
+ authors: ["Gene"],
+ description:
+ "네트워크의 기초 개념을 풍부한 그림과 함께 쉽게 이해할 수 있도록 설명한다.",
+ cover: "https://picsum.photos/seed/book10/300/400",
+ url: "https://www.yes24.com/Product/Goods/104369152",
+ price: 24000,
+ publisher: "영진닷컴",
+ publishedAt: "2022-05-20",
+ },
+ {
+ bookId: "book-011",
+ title: "Growing Object-Oriented Software, Guided by Tests",
+ authors: ["Steve Freeman", "Nat Pryce"],
+ description:
+ "TDD를 실전에서 적용하는 방법과 객체지향 설계 원칙을 함께 배울 수 있는 명저.",
+ cover: "https://picsum.photos/seed/book11/300/400",
+ url: "https://www.yes24.com/Product/Goods/3551451",
+ publisher: "Addison-Wesley",
+ publishedAt: "2009-10-12",
+ },
+ {
+ bookId: "book-012",
+ title: "컴퓨터 과학이 보이는 그림책",
+ authors: ["아다치 마사유키"],
+ description:
+ "컴퓨터 과학의 핵심 개념을 그림으로 직관적으로 이해할 수 있도록 구성한 입문서.",
+ cover: null,
+ url: "https://www.yes24.com/Product/Goods/66028583",
+ price: 16000,
+ publisher: "성안당",
+ publishedAt: "2019-02-15",
+ },
];
export async function fetchRecommendBooks(
- count = 4,
+ count?: number,
): Promise {
await new Promise((resolve) => setTimeout(resolve, 400));
- return MOCK_RECOMMEND_BOOKS.slice(0, count);
+ return count !== undefined
+ ? MOCK_RECOMMEND_BOOKS.slice(0, count)
+ : MOCK_RECOMMEND_BOOKS;
}
diff --git a/lib/mock/my-page-recommend-home.ts b/lib/mock/my-page-recommend-home.ts
index b1733cd..c444594 100644
--- a/lib/mock/my-page-recommend-home.ts
+++ b/lib/mock/my-page-recommend-home.ts
@@ -32,11 +32,74 @@ export const MOCK_RECOMMEND_HOME_POSTS: MyPageRecommendHomePost[] = [
summary: "레인지/리스트/해시 파티셔닝을 실제 서비스 케이스에 적용한 경험을 공유합니다.",
date: "2026-04-13T11:00:00Z",
},
+ {
+ contentId: "content-305",
+ title: "Jest + React Testing Library로 컴포넌트 테스트 작성하기",
+ sourceName: "medium",
+ thumbnail: "https://picsum.photos/seed/rec5/400/240",
+ summary: "단위 테스트부터 인터랙션 테스트까지 실전 패턴을 다룹니다.",
+ date: "2026-04-11T09:00:00Z",
+ },
+ {
+ contentId: "content-306",
+ title: "Spring Boot 3 마이그레이션 가이드 — Jakarta EE 전환과 주요 변경점",
+ sourceName: "kakao_tech",
+ thumbnail: null,
+ summary: "Spring Boot 2에서 3으로 업그레이드할 때 반드시 확인해야 할 사항들을 정리합니다.",
+ date: "2026-04-09T14:00:00Z",
+ },
+ {
+ contentId: "content-307",
+ title: "AWS Lambda + API Gateway로 서버리스 백엔드 구축하기",
+ sourceName: "velog",
+ thumbnail: "https://picsum.photos/seed/rec7/400/240",
+ date: "2026-04-07T10:00:00Z",
+ },
+ {
+ contentId: "content-308",
+ title: "Tailwind CSS v4 완전 가이드 — @theme 토큰과 CSS 변수 전환",
+ sourceName: "toss_tech",
+ thumbnail: "https://picsum.photos/seed/rec8/400/240",
+ summary: "v3에서 v4로 마이그레이션하는 방법과 새 토큰 시스템을 설명합니다.",
+ date: "2026-04-05T11:00:00Z",
+ },
+ {
+ contentId: "content-309",
+ title: "CI/CD 파이프라인 구축 — GitHub Actions로 자동 배포 환경 만들기",
+ sourceName: "naver_d2",
+ thumbnail: null,
+ summary: "테스트, 빌드, 배포를 자동화하는 전체 워크플로우를 구성하는 방법을 소개합니다.",
+ date: "2026-04-03T09:00:00Z",
+ },
+ {
+ contentId: "content-310",
+ title: "웹 접근성 A11y 실천 가이드 — WCAG 2.1 기준으로 코드 개선하기",
+ sourceName: "medium",
+ thumbnail: "https://picsum.photos/seed/rec10/400/240",
+ date: "2026-04-01T13:00:00Z",
+ },
+ {
+ contentId: "content-311",
+ title: "Storybook 8 업데이트 — CSF3 형식과 Play function 실전 활용",
+ sourceName: "kakao_tech",
+ thumbnail: "https://picsum.photos/seed/rec11/400/240",
+ summary: "Storybook 최신 버전의 핵심 변경점과 인터랙션 테스트 작성법을 살펴봅니다.",
+ date: "2026-03-30T10:00:00Z",
+ },
+ {
+ contentId: "content-312",
+ title: "OpenTelemetry로 분산 추적 구현하기 — 마이크로서비스 관찰 가능성 확보",
+ sourceName: "toss_tech",
+ thumbnail: null,
+ date: "2026-03-27T14:00:00Z",
+ },
];
export async function fetchRecommendHomePosts(
- count = 4,
+ count?: number,
): Promise {
await new Promise((resolve) => setTimeout(resolve, 400));
- return MOCK_RECOMMEND_HOME_POSTS.slice(0, count);
+ return count !== undefined
+ ? MOCK_RECOMMEND_HOME_POSTS.slice(0, count)
+ : MOCK_RECOMMEND_HOME_POSTS;
}
diff --git a/lib/mock/my-page-recommend-video.ts b/lib/mock/my-page-recommend-video.ts
index b74cbbb..fb7240a 100644
--- a/lib/mock/my-page-recommend-video.ts
+++ b/lib/mock/my-page-recommend-video.ts
@@ -41,11 +41,93 @@ export const MOCK_RECOMMEND_VIDEOS: MyPageRecommendVideo[] = [
views: 34800,
uploadedAt: "2026-04-07T00:00:00Z",
},
+ {
+ videoId: "vid-005",
+ title: "TypeScript 제네릭 완전 정복 — 실전 패턴 7가지",
+ channelName: "코딩애플",
+ thumbnail: "https://picsum.photos/seed/vid5/400/240",
+ url: "https://www.youtube.com/watch?v=example5",
+ duration: "22:15",
+ views: 78400,
+ uploadedAt: "2026-04-04T00:00:00Z",
+ },
+ {
+ videoId: "vid-006",
+ title: "Redis 기초부터 실전까지 — 캐싱 전략과 세션 관리",
+ channelName: "드림코딩",
+ thumbnail: null,
+ url: "https://www.youtube.com/watch?v=example6",
+ duration: "35:40",
+ views: 52100,
+ uploadedAt: "2026-04-01T00:00:00Z",
+ },
+ {
+ videoId: "vid-007",
+ title: "CI/CD 파이프라인 구축 — GitHub Actions 실전편",
+ channelName: "Fireship",
+ thumbnail: "https://picsum.photos/seed/vid7/400/240",
+ url: "https://www.youtube.com/watch?v=example7",
+ duration: "14:32",
+ views: 120500,
+ uploadedAt: "2026-03-28T00:00:00Z",
+ },
+ {
+ videoId: "vid-008",
+ title: "Spring Boot REST API 설계 — 실무 패턴과 예외 처리",
+ channelName: "우아한테크",
+ thumbnail: "https://picsum.photos/seed/vid8/400/240",
+ url: "https://www.youtube.com/watch?v=example8",
+ duration: "51:08",
+ views: 43700,
+ uploadedAt: "2026-03-25T00:00:00Z",
+ },
+ {
+ videoId: "vid-009",
+ title: "SQL 쿼리 최적화 — 인덱스 설계와 실행 계획 분석",
+ channelName: "데이터리안",
+ thumbnail: null,
+ url: "https://www.youtube.com/watch?v=example9",
+ duration: "38:20",
+ views: 29300,
+ uploadedAt: "2026-03-22T00:00:00Z",
+ },
+ {
+ videoId: "vid-010",
+ title: "Tailwind CSS v4 신기능 총정리 — 10분 핵심 정리",
+ channelName: "Traversy Media",
+ thumbnail: "https://picsum.photos/seed/vid10/400/240",
+ url: "https://www.youtube.com/watch?v=example10",
+ duration: "09:55",
+ views: 67800,
+ uploadedAt: "2026-03-18T00:00:00Z",
+ },
+ {
+ videoId: "vid-011",
+ title: "Zustand로 전역 상태 관리하기 — Redux 없이 깔끔하게",
+ channelName: "코딩애플",
+ thumbnail: "https://picsum.photos/seed/vid11/400/240",
+ url: "https://www.youtube.com/watch?v=example11",
+ duration: "16:47",
+ views: 91200,
+ uploadedAt: "2026-03-14T00:00:00Z",
+ },
+ {
+ videoId: "vid-012",
+ title: "웹 접근성 A11y 기초 — 스크린 리더 대응과 ARIA 사용법",
+ channelName: "드림코딩",
+ thumbnail: null,
+ url: "https://www.youtube.com/watch?v=example12",
+ duration: "24:11",
+ views: 18600,
+ uploadedAt: "2026-03-10T00:00:00Z",
+ },
];
export async function fetchRecommendVideos(
- count = 4,
+ count?: number,
): Promise {
await new Promise((resolve) => setTimeout(resolve, 400));
- return MOCK_RECOMMEND_VIDEOS.slice(0, count);
+ return count !== undefined
+ ? MOCK_RECOMMEND_VIDEOS.slice(0, count)
+ : MOCK_RECOMMEND_VIDEOS;
}
diff --git a/lib/mock/my-page-scraps.ts b/lib/mock/my-page-scraps.ts
index 49d2680..f322d01 100644
--- a/lib/mock/my-page-scraps.ts
+++ b/lib/mock/my-page-scraps.ts
@@ -74,6 +74,41 @@ export const MOCK_SCRAPS: MyPageScrap[] = [
summary:
"Zustand v4에서 v5로 업그레이드할 때 꼭 확인해야 할 변경사항들을 정리했습니다.",
},
+ {
+ id: "9",
+ contentId: "content-109",
+ title: "Tailwind CSS v4 마이그레이션 — @theme 토큰과 CSS 변수 전환",
+ sourceName: "kakao_tech",
+ thumbnail: "https://picsum.photos/seed/scrap9/400/240",
+ createdAt: "2026-03-30T10:00:00Z",
+ summary: "v3에서 v4로 넘어갈 때 바뀐 설정 방식과 토큰 시스템을 정리합니다.",
+ },
+ {
+ id: "10",
+ contentId: "content-110",
+ title: "Kubernetes 입문 — Pod, Service, Deployment 핵심 개념 정리",
+ sourceName: "naver_d2",
+ thumbnail: null,
+ createdAt: "2026-03-27T14:00:00Z",
+ summary: "컨테이너 오케스트레이션의 핵심 리소스를 예제와 함께 설명합니다.",
+ },
+ {
+ id: "11",
+ contentId: "content-111",
+ title: "웹 성능 최적화 — Core Web Vitals 개선 실전 사례",
+ sourceName: "toss_tech",
+ thumbnail: "https://picsum.photos/seed/scrap11/400/240",
+ createdAt: "2026-03-24T09:00:00Z",
+ summary: "LCP, CLS, INP 지표를 실제 서비스에서 개선한 경험을 공유합니다.",
+ },
+ {
+ id: "12",
+ contentId: "content-112",
+ title: "Git 브랜치 전략 — Trunk Based Development vs Git Flow 비교",
+ sourceName: "medium",
+ thumbnail: "https://picsum.photos/seed/scrap12/400/240",
+ createdAt: "2026-03-20T11:30:00Z",
+ },
];
export async function fetchMyScraps(): Promise {
diff --git a/lib/mock/my-page-wrong-quizzes.ts b/lib/mock/my-page-wrong-quizzes.ts
index fc3f607..1cb290d 100644
--- a/lib/mock/my-page-wrong-quizzes.ts
+++ b/lib/mock/my-page-wrong-quizzes.ts
@@ -104,6 +104,42 @@ export const MOCK_QUIZ_HISTORIES: MyPageQuizHistory[] = [
passed: false,
attemptedAt: "2026-04-01T15:00:00Z",
},
+ {
+ attemptId: "a10",
+ contentId: "content-210",
+ contentTitle: "Kubernetes Pod 스케줄링 전략 — Affinity와 Taint/Toleration",
+ thumbnail: "https://picsum.photos/seed/quiz10/400/240",
+ preview: "Kubernetes에서 Node Affinity와 Pod Affinity의 차이점은?",
+ level: "SENIOR",
+ score: 2,
+ totalQuestions: 5,
+ passed: false,
+ attemptedAt: "2026-03-29T11:00:00Z",
+ },
+ {
+ attemptId: "a11",
+ contentId: "content-211",
+ contentTitle: "웹 성능 최적화 — Core Web Vitals 개선 실전 사례",
+ thumbnail: "https://picsum.photos/seed/quiz11/400/240",
+ preview: "LCP 지표를 개선하기 위한 가장 효과적인 방법은 무엇인가요?",
+ level: "MIDDLE",
+ score: 3,
+ totalQuestions: 5,
+ passed: false,
+ attemptedAt: "2026-03-26T09:30:00Z",
+ },
+ {
+ attemptId: "a12",
+ contentId: "content-212",
+ contentTitle: "GraphQL vs REST — 실무에서 선택 기준과 트레이드오프",
+ thumbnail: null,
+ preview: "GraphQL의 N+1 문제를 해결하는 일반적인 방법은?",
+ level: "JUNIOR",
+ score: 1,
+ totalQuestions: 5,
+ passed: false,
+ attemptedAt: "2026-03-22T14:00:00Z",
+ },
];
export async function fetchMyWrongQuizzes(): Promise {