Skip to content
Open
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
34 changes: 34 additions & 0 deletions web/src/mocks/datas/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,38 @@ export const ARTICLES: Article[] = [
imageUrl: 'https://example.com/newsletter-image.jpg',
},
},
{
articleId: 6,
title: '일하는 방식을 바꾸는 생성형 AI 활용법',
contentsSummary:
'반복 업무를 줄이고 중요한 의사결정에 집중할 수 있는 생성형 AI 활용 사례를 소개합니다.',
arrivedDateTime: '2026.07.19T09:00:00',
thumbnailUrl:
'https://img.freepik.com/free-vector/artificial-intelligence-concept-illustration_114360-7000.jpg',
expectedReadTime: 6,
isRead: false,
isBookmarked: false,
newsletter: {
category: '커리어',
name: '폴인레터',
imageUrl: 'https://example.com/newsletter-image.jpg',
},
},
{
articleId: 7,
title: '이번 주 꼭 알아야 할 경제 이슈 다섯 가지',
contentsSummary:
'한 주의 주요 경제 소식과 일상에 미치는 영향을 쉽고 빠르게 정리해드립니다.',
arrivedDateTime: '2026.07.19T08:00:00',
thumbnailUrl:
'https://img.freepik.com/free-vector/business-analytics-concept-illustration_114360-878.jpg',
expectedReadTime: 5,
isRead: false,
isBookmarked: false,
newsletter: {
category: '경제',
name: '어피티',
imageUrl: 'https://example.com/newsletter-image.jpg',
},
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { Link } from '@tanstack/react-router';
import Badge from '@/components/Badge/Badge';
import ImageWithFallback from '@/components/ImageWithFallback/ImageWithFallback';
import { useDevice } from '@/hooks/useDevice';
import type { components } from '@/types/openapi';
import ClockIcon from '#/assets/svg/clock.svg';

Expand All @@ -10,6 +11,7 @@ interface NewsletterItemCardProps {
}

export default function NewsletterItemCard({ data }: NewsletterItemCardProps) {
const device = useDevice();
const {
articleId,
title,
Expand All @@ -21,11 +23,17 @@ export default function NewsletterItemCard({ data }: NewsletterItemCardProps) {

return (
<Container to={`/articles/${articleId}`}>
<NewsletterImage
src={thumbnailUrl ?? newsletter?.imageUrl ?? ''}
alt={title ?? ''}
height={180}
/>
<ImageWrapper>
<NewsletterImage
src={thumbnailUrl ?? newsletter?.imageUrl ?? ''}
alt={title ?? ''}
/>
{device === 'mobile' && (
<CategoryBadgeBox>
<Badge text={newsletter?.category ?? ''} />
</CategoryBadgeBox>
)}
</ImageWrapper>

<ContentWrapper>
<TextContent>
Expand All @@ -34,9 +42,9 @@ export default function NewsletterItemCard({ data }: NewsletterItemCardProps) {
</TextContent>

<MetaContent>
<Badge text={newsletter?.category ?? ''} />
{device !== 'mobile' && <Badge text={newsletter?.category ?? ''} />}
<MetaInfo>
<SourceText>from {newsletter?.name ?? ''}</SourceText>
<NewsletterName>from {newsletter?.name ?? ''}</NewsletterName>
<ReadTimeBox>
<ClockIcon width={16} height={16} />
<SourceText>{`${expectedReadTime}분`}</SourceText>
Expand Down Expand Up @@ -65,13 +73,25 @@ const Container = styled(Link)`
transition: all 0.2s ease;
`;

const ImageWrapper = styled.div`
position: relative;
`;

const NewsletterImage = styled(ImageWithFallback)`
width: 100%;

aspect-ratio: 16 / 9;

object-fit: cover;
object-position: center;
`;

const CategoryBadgeBox = styled.div`
position: absolute;
top: 8px;
left: 8px;
`;

const ContentWrapper = styled.div`
padding: 20px;

Expand All @@ -92,7 +112,7 @@ const Title = styled.h3`
display: -webkit-box;

color: ${({ theme }) => theme.colors.textPrimary};
font: ${({ theme }) => theme.fonts.t7Bold};
font: ${({ theme }) => theme.fonts.t6Bold};

-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
Expand All @@ -113,20 +133,35 @@ const Description = styled.p`
`;

const MetaContent = styled.div`
min-width: 0;

display: flex;
gap: 8px;
align-items: center;
`;

const MetaInfo = styled.div`
min-width: 0;

display: flex;
gap: 8px;
align-items: center;
`;

const NewsletterName = styled.span`
overflow: hidden;

color: ${({ theme }) => theme.colors.textTertiary};
font: ${({ theme }) => theme.fonts.t3Regular};
white-space: nowrap;

text-overflow: ellipsis;
`;

const SourceText = styled.span`
color: ${({ theme }) => theme.colors.textTertiary};
font: ${({ theme }) => theme.fonts.t3Regular};
white-space: nowrap;
`;

const ReadTimeBox = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { useMemo } from 'react';
import EmptyUnreadCard from '../EmptyUnreadCard/EmptyUnreadCard';
import NewsletterItemCard from '../NewsletterItemCard/NewsletterItemCard';
import { queries } from '@/apis/queries';
import { useDevice } from '@/hooks/useDevice';
import { formatDate } from '@/utils/date';
import type { Device } from '@/hooks/useDevice';

interface TodayUnreadArticlesSectionProps {
articleId: number;
Expand All @@ -19,7 +17,6 @@ const TodayUnreadArticlesSection = ({
const { data: todayArticles } = useQuery(
queries.articles({ date: formatDate(today, '-') }),
);
const device = useDevice();

const unreadArticles = todayArticles?.content?.filter(
(article) => !article.isRead && article.articleId !== articleId,
Expand All @@ -29,8 +26,8 @@ const TodayUnreadArticlesSection = ({
<Container>
<TodayArticleTitle>오늘 읽지 않은 다른 아티클</TodayArticleTitle>
{unreadArticles?.length && unreadArticles.length > 0 ? (
<TodayArticleList device={device}>
{unreadArticles?.map((article) => (
<TodayArticleList>
{unreadArticles.map((article) => (
<NewsletterItemCard key={article.articleId} data={article} />
))}
</TodayArticleList>
Expand Down Expand Up @@ -58,11 +55,10 @@ const TodayArticleTitle = styled.h3`
font: ${({ theme }) => theme.fonts.t7Bold};
`;

const TodayArticleList = styled.div<{ device: Device }>`
const TodayArticleList = styled.div`
display: grid;
gap: 20px;
justify-items: center;

grid-template-columns: ${({ device }) =>
`repeat(${device === 'pc' ? 2 : 1}, 1fr)`};
grid-template-columns: repeat(2, 1fr);
`;
Loading