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
5 changes: 3 additions & 2 deletions components/features/home/SourceLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isStackOverflowSource } from "@/lib/content/sourceGuards";
interface SourceLogoProps {
sourceName: string;
size?: number;
paddingOverride?: number;
}

type LogoConfig = {
Expand Down Expand Up @@ -68,7 +69,7 @@ const SOURCE_LOGO: Record<string, LogoConfig> = {
nextjs_blog: { src: "/icons/sources/nextjs_blog.svg", bg: "#FFFFFF" },
};

export function SourceLogo({ sourceName, size = 17 }: SourceLogoProps) {
export function SourceLogo({ sourceName, size = 17, paddingOverride }: SourceLogoProps) {
const key = sourceName.trim().toLowerCase();

let config: LogoConfig | undefined;
Expand All @@ -82,7 +83,7 @@ export function SourceLogo({ sourceName, size = 17 }: SourceLogoProps) {
}

if (config) {
const pad = config.innerPadding ?? 0;
const pad = paddingOverride !== undefined ? paddingOverride : (config.innerPadding ?? 0);
const innerSize = size - pad * 2;
return (
<span
Expand Down
22 changes: 14 additions & 8 deletions components/features/my-page/recommend/RecommendedHomePostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ export function RecommendedHomePostCard({
}: {
post: MyPageRecommendHomePost;
}) {
const { contentId, title, sourceName, thumbnail, date } = post;
const { id, title, translatedTitle, sourceName, thumbnailUrl, publishedAt } =
post;
const displayTitle = translatedTitle ?? title;

return (
<Link href={`/home/${contentId}`}>
<Link href={`/home/${id}`}>
<div className="group flex h-full flex-col overflow-hidden rounded-md border border-border bg-card">
<div className="relative aspect-video w-full overflow-hidden bg-muted">
{thumbnail ? (
{thumbnailUrl ? (
<Image
fill
src={thumbnail}
alt={title}
src={thumbnailUrl}
alt={displayTitle}
className="object-cover transition-transform duration-200"
/>
) : (
Expand All @@ -32,14 +34,18 @@ export function RecommendedHomePostCard({

<div className="flex flex-1 flex-col gap-1.5 p-3">
<div className="flex items-center gap-1.5">
<SourceLogo sourceName={sourceName} size={13} />
<SourceLogo
sourceName={sourceName}
size={13}
{...(sourceName === "naver_d2" && { paddingOverride: 4 })}
/>
<span className="text-xs text-muted-foreground">{sourceName}</span>
</div>
<p className="line-clamp-2 flex-1 text-sm font-medium leading-snug tracking-[-0.01em] text-foreground">
{title}
{displayTitle}
</p>
<span className="text-xs text-muted-foreground">
{formatDate(date)}
{formatDate(publishedAt)}
</span>
</div>
</div>
Expand Down
47 changes: 20 additions & 27 deletions components/features/my-page/recommend/RecommendedHomePostList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"use client";

import { useEffect, useState, useMemo } from "react";
import { useEffect, useState } 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;
import type { MyPageRecommendContentsResponse } from "@/types/myPage";

function ListItemSkeleton() {
return (
Expand All @@ -24,24 +21,18 @@ function ListItemSkeleton() {
}

export function RecommendedHomePostList() {
const [posts, setPosts] = useState<MyPageRecommendHomePost[]>([]);
const [postsData, setPostsData] =
useState<MyPageRecommendContentsResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
fetchRecommendHomePosts()
.then((data) => setPosts(data))
.then((data) => setPostsData(data))
.catch(() => setIsError(true))
.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 All @@ -60,6 +51,16 @@ export function RecommendedHomePostList() {
);
}

if (!postsData?.isPersonalized) {
return (
<p className="py-10 text-center text-sm text-muted-foreground">
{postsData?.message ?? "아직 추천할 글이 부족해요. 더 많은 글을 읽어보세요!"}
</p>
);
}

const posts = postsData.contents;

if (posts.length === 0) {
return (
<p className="py-10 text-center text-sm text-muted-foreground">
Expand All @@ -69,18 +70,10 @@ export function RecommendedHomePostList() {
}

return (
<>
<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"
/>
</>
<div className="divide-y divide-border">
{posts.map((post) => (
<RecommendedHomePostListItem key={post.id} post={post} />
))}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,30 @@ export function RecommendedHomePostListItem({
}: {
post: MyPageRecommendHomePost;
}) {
const { contentId, title, sourceName, thumbnail, summary, date } = post;
const {
id,
title,
translatedTitle,
sourceName,
thumbnailUrl,
preview,
publishedAt,
} = post;
const displayTitle = translatedTitle ?? title;

return (
<Link
href={`/home/${contentId}`}
href={`/home/${id}`}
className="-mx-2 flex gap-4 px-2 py-3 transition-colors"
>
<div className="relative aspect-[3/2] w-36 shrink-0 overflow-hidden rounded-sm bg-muted">
{thumbnail ? (
<Image fill src={thumbnail} alt={title} className="object-cover" />
{thumbnailUrl ? (
<Image
fill
src={thumbnailUrl}
alt={displayTitle}
className="object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center">
<FileText className="h-5 w-5 text-muted-foreground/30" />
Expand All @@ -29,19 +43,25 @@ export function RecommendedHomePostListItem({

<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex items-center gap-1.5">
<SourceLogo sourceName={sourceName} size={13} />
<SourceLogo
sourceName={sourceName}
size={13}
{...(sourceName === "naver_d2" && { paddingOverride: 4 })}
/>
<span className="text-xs font-medium text-muted-foreground">
{sourceName}
</span>
</div>
<p className="line-clamp-2 text-[15px] font-semibold leading-snug tracking-[-0.01em] text-foreground">
{title}
{displayTitle}
</p>
{summary && (
<p className="line-clamp-1 text-xs text-muted-foreground">{summary}</p>
{preview && (
<p className="line-clamp-1 text-xs text-muted-foreground">
{preview}
</p>
)}
<span className="mt-auto pt-1 text-xs text-muted-foreground">
{formatDate(date)}
{formatDate(publishedAt)}
</span>
</div>
</Link>
Expand Down
18 changes: 13 additions & 5 deletions components/features/my-page/recommend/RecommendedSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { fetchRecommendHomePosts } from "@/lib/mock/my-page-recommend-home";
import { fetchRecommendVideos } from "@/lib/mock/my-page-recommend-video";
import { fetchRecommendBooks } from "@/lib/mock/my-page-recommend-book";
import type {
MyPageRecommendHomePost,
MyPageRecommendContentsResponse,
MyPageRecommendVideo,
MyPageRecommendBook,
} from "@/types/myPage";
Expand Down Expand Up @@ -58,7 +58,8 @@ function BookCardSkeleton() {
}

export function RecommendedSection() {
const [homePosts, setHomePosts] = useState<MyPageRecommendHomePost[]>([]);
const [homePostsData, setHomePostsData] =
useState<MyPageRecommendContentsResponse | null>(null);
const [videos, setVideos] = useState<MyPageRecommendVideo[]>([]);
const [books, setBooks] = useState<MyPageRecommendBook[]>([]);
const [isLoading, setIsLoading] = useState(true);
Expand All @@ -70,15 +71,17 @@ export function RecommendedSection() {
fetchRecommendVideos(4),
fetchRecommendBooks(4),
])
.then(([posts, vids, bks]) => {
setHomePosts(posts);
.then(([postsData, vids, bks]) => {
setHomePostsData(postsData);
setVideos(vids);
setBooks(bks);
})
.catch(() => setIsError(true))
.finally(() => setIsLoading(false));
}, []);

const homePosts = homePostsData?.contents ?? [];

if (isError) {
return (
<section>
Expand Down Expand Up @@ -107,12 +110,17 @@ export function RecommendedSection() {
<CardSkeleton key={i} />
))}
</div>
) : !homePostsData?.isPersonalized ? (
<p className="text-sm text-muted-foreground">
{homePostsData?.message ??
"아직 추천할 글이 부족해요. 더 많은 글을 읽어보세요!"}
</p>
) : homePosts.length === 0 ? (
<p className="text-sm text-muted-foreground">추천 글이 없습니다.</p>
) : (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
{homePosts.map((post) => (
<RecommendedHomePostCard key={post.contentId} post={post} />
<RecommendedHomePostCard key={post.id} post={post} />
))}
</div>
)}
Expand Down
Loading
Loading