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
2 changes: 1 addition & 1 deletion components/features/my-page/MyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RecommendedSection } from "./recommend/RecommendedSection";

export default function MyPage() {
return (
<div className="mx-auto max-w-5xl space-y-10 px-4 py-8 lg:px-8">
<div className="mx-auto max-w-5xl space-y-10 px-4 pt-8 pb-12 lg:px-8">
<div>
<h1 className="text-xl font-bold tracking-[-0.01em] text-foreground md:text-2xl">
마이페이지
Expand Down
82 changes: 82 additions & 0 deletions components/features/my-page/MyPagePagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use client";

import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";

interface MyPagePaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
className?: string;
}

export function MyPagePagination({
currentPage,
totalPages,
onPageChange,
className,
}: MyPagePaginationProps) {
if (totalPages <= 1) return null;

const getPageNumbers = () => {
const pages: (number | "...")[] = [];
if (totalPages <= 7) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
pages.push(1);
if (currentPage > 4) pages.push("...");
const start = Math.max(2, currentPage - 2);
const end = Math.min(totalPages - 1, currentPage + 2);
for (let i = start; i <= end; i++) pages.push(i);
if (currentPage < totalPages - 3) pages.push("...");
pages.push(totalPages);
return pages;
};

return (
<div className={cn("flex items-center justify-center gap-1", className)}>
<button
type="button"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
className="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-30 cursor-pointer"
>
<ChevronLeft className="h-4 w-4" />
</button>

{getPageNumbers().map((page, i) =>
page === "..." ? (
<span
key={`ellipsis-${i}`}
className="flex h-8 w-8 items-center justify-center text-sm text-muted-foreground"
>
</span>
) : (
<button
key={page}
type="button"
onClick={() => onPageChange(page as number)}
className={cn(
"flex h-8 w-8 items-center justify-center rounded-lg text-sm font-medium transition-colors cursor-pointer",
currentPage === page
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted",
)}
>
{page}
</button>
),
)}

<button
type="button"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-30 cursor-pointer"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
);
}
30 changes: 24 additions & 6 deletions components/features/my-page/quizzes/WrongQuizList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { WrongQuizListItem } from "./WrongQuizListItem";
import { MyPagePagination } from "../MyPagePagination";
import type { MyPageQuizHistory } from "@/types/myPage";

const PAGE_SIZE = 10;

type SortOrder = "newest" | "oldest";

export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
const [sort, setSort] = useState<SortOrder>("newest");
const [currentPage, setCurrentPage] = useState(1);

const sorted = useMemo(() => {
return [...quizzes].sort((a, b) => {
Expand All @@ -25,6 +29,12 @@ export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
});
}, [quizzes, sort]);

const totalPages = Math.ceil(sorted.length / PAGE_SIZE);
const pagedItems = sorted.slice(
(currentPage - 1) * PAGE_SIZE,
currentPage * PAGE_SIZE,
);

return (
<div>
<div className="mb-4 flex items-center justify-between">
Expand All @@ -37,7 +47,7 @@ export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
<DropdownMenuContent align="end" className="min-w-[6rem] p-1">
<DropdownMenuRadioGroup
value={sort}
onValueChange={(v) => setSort(v as SortOrder)}
onValueChange={(v) => { setSort(v as SortOrder); setCurrentPage(1); }}
>
<DropdownMenuRadioItem className="cursor-pointer" value="newest">
최신순
Expand All @@ -55,11 +65,19 @@ export function WrongQuizList({ quizzes }: { quizzes: MyPageQuizHistory[] }) {
틀린 퀴즈가 없습니다.
</p>
) : (
<div className="divide-y divide-border">
{sorted.map((quiz) => (
<WrongQuizListItem key={quiz.attemptId} quiz={quiz} />
))}
</div>
<>
<div className="divide-y divide-border">
{pagedItems.map((quiz) => (
<WrongQuizListItem key={quiz.attemptId} quiz={quiz} />
))}
</div>
<MyPagePagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
className="mt-8 mb-12"
/>
</>
)}
</div>
);
Expand Down
30 changes: 24 additions & 6 deletions components/features/my-page/recommend/RecommendedBookList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="-mx-2 flex gap-4 px-2 py-3">
Expand Down Expand Up @@ -40,6 +43,7 @@ export function RecommendedBookList() {
const [books, setBooks] = useState<MyPageRecommendBook[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
fetchRecommendBooks()
Expand All @@ -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 (
<p className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -75,10 +85,18 @@ export function RecommendedBookList() {
}

return (
<div className="divide-y divide-border">
{books.map((book) => (
<RecommendedBookListItem key={book.bookId} book={book} />
))}
</div>
<>
<div className="divide-y divide-border">
{pagedItems.map((book) => (
<RecommendedBookListItem key={book.bookId} book={book} />
))}
</div>
<MyPagePagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
className="mt-8 mb-12"
/>
</>
);
}
30 changes: 24 additions & 6 deletions components/features/my-page/recommend/RecommendedHomePostList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="-mx-2 flex gap-4 px-2 py-3">
Expand All @@ -24,6 +27,7 @@ export function RecommendedHomePostList() {
const [posts, setPosts] = useState<MyPageRecommendHomePost[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
fetchRecommendHomePosts()
Expand All @@ -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 (
<p className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -59,10 +69,18 @@ export function RecommendedHomePostList() {
}

return (
<div className="divide-y divide-border">
{posts.map((post) => (
<RecommendedHomePostListItem key={post.contentId} post={post} />
))}
</div>
<>
<div className="divide-y divide-border">
{pagedItems.map((post) => (
<RecommendedHomePostListItem key={post.contentId} post={post} />
))}
</div>
<MyPagePagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
className="mt-8 mb-12"
/>
</>
);
}
30 changes: 24 additions & 6 deletions components/features/my-page/recommend/RecommendedVideoList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="-mx-2 flex gap-4 px-2 py-3">
Expand All @@ -23,6 +26,7 @@ export function RecommendedVideoList() {
const [videos, setVideos] = useState<MyPageRecommendVideo[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
fetchRecommendVideos()
Expand All @@ -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 (
<p className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -58,10 +68,18 @@ export function RecommendedVideoList() {
}

return (
<div className="divide-y divide-border">
{videos.map((video) => (
<RecommendedVideoListItem key={video.videoId} video={video} />
))}
</div>
<>
<div className="divide-y divide-border">
{pagedItems.map((video) => (
<RecommendedVideoListItem key={video.videoId} video={video} />
))}
</div>
<MyPagePagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
className="mt-8 mb-12"
/>
</>
);
}
Loading
Loading