From e4208c179c9c10244fc91dd04cbdc4bba8454bcb Mon Sep 17 00:00:00 2001 From: Joodongkim Date: Mon, 28 Oct 2024 19:19:54 +0900 Subject: [PATCH] =?UTF-8?q?Merge=20branch=20'Next-=EA=B9=80=EC=A3=BC?= =?UTF-8?q?=EB=8F=99'=20of=20https://github.com/joodongkim/10-Sprint-Missi?= =?UTF-8?q?on=20into=20Next-=EA=B9=80=EC=A3=BC=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/boards/AllArticlesSection.tsx | 10 +- .../boards/BestArticlesSection.styles.ts | 44 +++++++ components/boards/BestArticlesSection.tsx | 40 +----- .../items/itemPage/CommentThread.styles.ts | 57 +++++++++ components/items/itemPage/CommentThread.tsx | 64 +++------- .../itemPage/ItemCommentSection.styles.ts | 53 ++++++++ .../items/itemPage/ItemCommentSection.tsx | 57 ++------- .../itemPage/ItemProfileSection.styles.ts | 112 +++++++++++++++++ .../items/itemPage/ItemProfileSection.tsx | 116 +++--------------- .../items/itemPage/LikeButton.styles.ts | 22 ++++ components/items/itemPage/LikeButton.tsx | 28 +---- .../items/itemPage/TagDisplay.styles.ts | 17 +++ components/items/itemPage/TagDisplay.tsx | 20 +-- .../marketPage/AllItemsSection.styles.ts | 23 ++++ .../items/marketPage/AllItemsSection.tsx | 26 +--- .../marketPage/BestItemsSection.styles.ts | 27 ++++ .../items/marketPage/BestItemsSection.tsx | 28 +---- .../items/marketPage/ItemCard.styles.ts | 46 +++++++ components/items/marketPage/ItemCard.tsx | 48 ++------ components/layout/Layout.tsx | 4 +- components/ui/DropdownMenu.styles.ts | 42 +++++++ components/ui/DropdownMenu.tsx | 50 ++------ components/ui/EmptyState.styles.ts | 17 +++ components/ui/EmptyState.tsx | 16 +-- components/ui/Icon.styles.ts | 26 ++++ components/ui/Icon.tsx | 31 +---- components/ui/LikeCountDisplay.styles.ts | 13 ++ components/ui/LikeCountDisplay.tsx | 18 +-- components/ui/LoadingSpinner.styles.ts | 26 ++++ components/ui/LoadingSpinner.tsx | 31 +---- components/ui/PaginationBar.tsx | 6 +- components/ui/SearchBar.styles.ts | 27 ++++ components/ui/SearchBar.tsx | 32 +---- pages/boards/index.styles.ts | 8 ++ pages/boards/index.tsx | 7 +- pages/items/[id].styles.ts | 13 ++ pages/items/[id].tsx | 15 +-- types/articleTypes.ts | 4 +- 38 files changed, 680 insertions(+), 544 deletions(-) create mode 100644 components/boards/BestArticlesSection.styles.ts create mode 100644 components/items/itemPage/CommentThread.styles.ts create mode 100644 components/items/itemPage/ItemCommentSection.styles.ts create mode 100644 components/items/itemPage/ItemProfileSection.styles.ts create mode 100644 components/items/itemPage/LikeButton.styles.ts create mode 100644 components/items/itemPage/TagDisplay.styles.ts create mode 100644 components/items/marketPage/AllItemsSection.styles.ts create mode 100644 components/items/marketPage/BestItemsSection.styles.ts create mode 100644 components/items/marketPage/ItemCard.styles.ts create mode 100644 components/ui/DropdownMenu.styles.ts create mode 100644 components/ui/EmptyState.styles.ts create mode 100644 components/ui/Icon.styles.ts create mode 100644 components/ui/LikeCountDisplay.styles.ts create mode 100644 components/ui/LoadingSpinner.styles.ts create mode 100644 components/ui/SearchBar.styles.ts create mode 100644 pages/boards/index.styles.ts create mode 100644 pages/items/[id].styles.ts diff --git a/components/boards/AllArticlesSection.tsx b/components/boards/AllArticlesSection.tsx index 1a0f0990..8d5027dc 100644 --- a/components/boards/AllArticlesSection.tsx +++ b/components/boards/AllArticlesSection.tsx @@ -25,11 +25,11 @@ import { useRouter } from "next/router"; import { ItemContainer, ArticleInfoDiv, AddArticleLink } from "./AllArticlesSection.styles"; -interface ArticleItemProps { +type ArticleItemProps = { article: Article; } -const ArticleItem: React.FC = ({ article }) => { +const ArticleItem = ({ article }: ArticleItemProps) => { const dateString = format(article.createdAt, "yyyy. MM. dd"); return ( @@ -65,13 +65,11 @@ const ArticleItem: React.FC = ({ article }) => { ); }; -interface AllArticlesSectionProps { +type AllArticlesSectionProps = { initialArticles: Article[]; } -const AllArticlesSection: React.FC = ({ - initialArticles, -}) => { +const AllArticlesSection = ({initialArticles}: AllArticlesSectionProps) => { const [orderBy, setOrderBy] = useState("recent"); const [articles, setArticles] = useState(initialArticles); diff --git a/components/boards/BestArticlesSection.styles.ts b/components/boards/BestArticlesSection.styles.ts new file mode 100644 index 00000000..92ceee5a --- /dev/null +++ b/components/boards/BestArticlesSection.styles.ts @@ -0,0 +1,44 @@ +import styled from "styled-components"; +import Link from "next/link"; +import { + FlexRowCentered, +} from "@/styles/CommonStyles"; + +const CardContainer = styled(Link)` + background-color: var(--gray-50); + border-radius: 8px; +`; + +const ContentWrapper = styled.div` + padding: 16px 24px; +`; + +const BestSticker = styled(FlexRowCentered)` + background-color: var(--blue); + border-radius: 0 0 32px 32px; + font-size: 16px; + font-weight: 600; + color: #fff; + gap: 4px; + padding: 6px 24px 8px 24px; + margin-left: 24px; + display: inline-flex; +`; + + +const BestArticlesCardSection = styled.div` + display: grid; + grid-template-columns: repeat(1, 1fr); + + @media ${({ theme }) => theme.mediaQuery.tablet} { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } + + @media ${({ theme }) => theme.mediaQuery.desktop} { + grid-template-columns: repeat(3, 1fr); + gap: 24px; + } +`; + +export { CardContainer, ContentWrapper, BestSticker, BestArticlesCardSection }; diff --git a/components/boards/BestArticlesSection.tsx b/components/boards/BestArticlesSection.tsx index 5eef8568..2fd95124 100644 --- a/components/boards/BestArticlesSection.tsx +++ b/components/boards/BestArticlesSection.tsx @@ -1,10 +1,8 @@ import { useEffect, useState } from "react"; -import styled from "styled-components"; import Image from "next/image"; -import Link from "next/link"; import { format } from "date-fns"; + import { - FlexRowCentered, SectionHeader, SectionTitle, } from "@/styles/CommonStyles"; @@ -22,26 +20,7 @@ import MedalIcon from "@/public/images/icons/ic_medal.svg"; import useViewport from "@/hooks/useViewport"; import LikeCountDisplay from "@/components/ui/LikeCountDisplay"; -const CardContainer = styled(Link)` - background-color: var(--gray-50); - border-radius: 8px; -`; - -const ContentWrapper = styled.div` - padding: 16px 24px; -`; - -const BestSticker = styled(FlexRowCentered)` - background-color: var(--blue); - border-radius: 0 0 32px 32px; - font-size: 16px; - font-weight: 600; - color: #fff; - gap: 4px; - padding: 6px 24px 8px 24px; - margin-left: 24px; - display: inline-flex; -`; +import { CardContainer, ContentWrapper, BestSticker, BestArticlesCardSection } from "./BestArticlesSection.styles"; const BestArticleCard = ({ article }: { article: Article }) => { const dateString = format(article.createdAt, "yyyy. MM. dd"); @@ -82,21 +61,6 @@ const BestArticleCard = ({ article }: { article: Article }) => { ); }; -const BestArticlesCardSection = styled.div` - display: grid; - grid-template-columns: repeat(1, 1fr); - - @media ${({ theme }) => theme.mediaQuery.tablet} { - grid-template-columns: repeat(2, 1fr); - gap: 16px; - } - - @media ${({ theme }) => theme.mediaQuery.desktop} { - grid-template-columns: repeat(3, 1fr); - gap: 24px; - } -`; - /** * Determines the appropriate page size for the best articles section based on the viewport width. * diff --git a/components/items/itemPage/CommentThread.styles.ts b/components/items/itemPage/CommentThread.styles.ts new file mode 100644 index 00000000..bbf887e5 --- /dev/null +++ b/components/items/itemPage/CommentThread.styles.ts @@ -0,0 +1,57 @@ +import styled from "styled-components"; + + +const CommentContainer = styled.div` + padding: 24px 0; + position: relative; +`; + +const SeeMoreButton = styled.button` + position: absolute; + right: 0; +`; + +const CommentContent = styled.p` + font-size: 16px; + line-height: 140%; + margin-bottom: 24px; +`; + +const AuthorProfile = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const UserProfileImage = styled.img` + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +`; + +const Username = styled.p` + color: var(--gray-600); + font-size: 14px; + margin-bottom: 4px; +`; + +const Timestamp = styled.p` + color: ${({ theme }) => theme.colors.gray[400]}; + font-size: 12px; +`; + +const ThreadContainer = styled.div` + margin-bottom: 40px; +`; + +export { + CommentContainer, + SeeMoreButton, + CommentContent, + AuthorProfile, + UserProfileImage, + Username, + Timestamp, + ThreadContainer, +}; \ No newline at end of file diff --git a/components/items/itemPage/CommentThread.tsx b/components/items/itemPage/CommentThread.tsx index 092b7fa5..1fbb061f 100644 --- a/components/items/itemPage/CommentThread.tsx +++ b/components/items/itemPage/CommentThread.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; import { getProductComments } from "@/api/itemApi"; -import styled from "styled-components"; import SeeMoreIcon from "@/public/images/icons/ic_kebab.svg"; import DefaultProfileImage from "@/public/images/ui/ic_profile.svg"; import { LineDivider } from "@/styles/CommonStyles"; @@ -11,51 +10,22 @@ import { } from "@/types/commentTypes"; import EmptyState from "@/components/ui/EmptyState"; -const CommentContainer = styled.div` - padding: 24px 0; - position: relative; -`; - -const SeeMoreButton = styled.button` - position: absolute; - right: 0; -`; - -const CommentContent = styled.p` - font-size: 16px; - line-height: 140%; - margin-bottom: 24px; -`; - -const AuthorProfile = styled.div` - display: flex; - align-items: center; - gap: 8px; -`; - -const UserProfileImage = styled.img` - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; -`; - -const Username = styled.p` - color: var(--gray-600); - font-size: 14px; - margin-bottom: 4px; -`; - -const Timestamp = styled.p` - color: ${({ theme }) => theme.colors.gray[400]}; - font-size: 12px; -`; - -interface CommentItemProps { +import { + CommentContainer, + SeeMoreButton, + CommentContent, + AuthorProfile, + UserProfileImage, + Username, + Timestamp, + ThreadContainer, +} from "./CommentThread.styles"; + +type CommentItemProps = { item: ProductComment; } -const CommentItem: React.FC = ({ item }) => { +const CommentItem = ({ item }: CommentItemProps) => { const authorInfo = item.writer; const formattedTimestamp = formatUpdatedAt(item.updatedAt); @@ -86,15 +56,11 @@ const CommentItem: React.FC = ({ item }) => { ); }; -const ThreadContainer = styled.div` - margin-bottom: 40px; -`; - -interface CommentThreadProps { +type CommentThreadProps = { productId: number; } -const CommentThread: React.FC = ({ productId }) => { +const CommentThread = ({ productId }: CommentThreadProps) => { const [comments, setComments] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); diff --git a/components/items/itemPage/ItemCommentSection.styles.ts b/components/items/itemPage/ItemCommentSection.styles.ts new file mode 100644 index 00000000..67cb5b38 --- /dev/null +++ b/components/items/itemPage/ItemCommentSection.styles.ts @@ -0,0 +1,53 @@ +import styled from "styled-components"; +import { Button } from "@/styles/CommonStyles"; + +const CommentInputSection = styled.section` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const SectionTitle = styled.h1` + font-size: 16px; + font-weight: 600; +`; + +const TextArea = styled.textarea` + background-color: ${({ theme }) => theme.colors.gray[100]}; + border: none; + border-radius: 12px; + padding: 16px 24px; + height: 104px; + resize: none; + + &::placeholder { + color: ${({ theme }) => theme.colors.gray[400]}; + font-size: 14px; + line-height: 24px; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + font-size: 16px; + } + } + + &:focus { + outline-color: ${({ theme }) => theme.colors.blue.primary}; + } +`; + +const PostCommentButton = styled(Button)` + align-self: flex-end; + font-weight: 600; + font-size: 14px; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + font-size: 16px; + } +`; + +export { + CommentInputSection, + SectionTitle, + TextArea, + PostCommentButton, +}; diff --git a/components/items/itemPage/ItemCommentSection.tsx b/components/items/itemPage/ItemCommentSection.tsx index ee61a9f8..a9e324eb 100644 --- a/components/items/itemPage/ItemCommentSection.tsx +++ b/components/items/itemPage/ItemCommentSection.tsx @@ -1,62 +1,21 @@ import { ChangeEvent, useState } from "react"; -import styled from "styled-components"; -import { Button } from "@/styles/CommonStyles"; import CommentThread from "./CommentThread"; const COMMENT_PLACEHOLDER = "개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다."; -const CommentInputSection = styled.section` - display: flex; - flex-direction: column; - gap: 16px; -`; +import { + CommentInputSection, + SectionTitle, + TextArea, + PostCommentButton, +} from "./ItemCommentSection.styles"; -const SectionTitle = styled.h1` - font-size: 16px; - font-weight: 600; -`; - -const TextArea = styled.textarea` - background-color: ${({ theme }) => theme.colors.gray[100]}; - border: none; - border-radius: 12px; - padding: 16px 24px; - height: 104px; - resize: none; - - &::placeholder { - color: ${({ theme }) => theme.colors.gray[400]}; - font-size: 14px; - line-height: 24px; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - font-size: 16px; - } - } - - &:focus { - outline-color: ${({ theme }) => theme.colors.blue.primary}; - } -`; - -const PostCommentButton = styled(Button)` - align-self: flex-end; - font-weight: 600; - font-size: 14px; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - font-size: 16px; - } -`; - -interface ItemCommentSectionProps { +type ItemCommentSectionProps = { productId: number; } -const ItemCommentSection: React.FC = ({ - productId, -}) => { +const ItemCommentSection = ({ productId}: ItemCommentSectionProps) => { const [comment, setComment] = useState(""); const handleInputChange = (e: ChangeEvent) => { diff --git a/components/items/itemPage/ItemProfileSection.styles.ts b/components/items/itemPage/ItemProfileSection.styles.ts new file mode 100644 index 00000000..1b840324 --- /dev/null +++ b/components/items/itemPage/ItemProfileSection.styles.ts @@ -0,0 +1,112 @@ +import styled from "styled-components"; + +const SectionContainer = styled.section` + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + flex-direction: row; + } + + @media ${({ theme }) => theme.mediaQuery.desktop} { + gap: 24px; + } +`; + +const ItemImageContainer = styled.div` + width: 100%; + flex: 1; + aspect-ratio: 1/1; + border-radius: 12px; + overflow: hidden; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + width: 40%; + max-width: 486px; + } +`; + +const ImageWrapper = styled.div` + width: 100%; + height: 100%; + position: relative; +`; + +const ItemDetailsContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; + align-items: flex-start; +`; + +const MainDetails = styled.div` + width: 100%; + position: relative; +`; + +const SeeMoreButton = styled.button` + position: absolute; + right: 0; +`; + +const ItemTitle = styled.h1` + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + font-size: 20px; + margin-bottom: 12px; + } + + @media ${({ theme }) => theme.mediaQuery.desktop} { + font-size: 24px; + margin-bottom: 16px; + } +`; + +const ItemPrice = styled.h2` + font-size: 24px; + font-weight: 600; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + font-size: 32px; + } + + @media ${({ theme }) => theme.mediaQuery.desktop} { + font-size: 40px; + } +`; + +const Description = styled.p` + font-size: 16px; + line-height: 140%; +`; + +const SectionLabel = styled.h3` + color: var(--gray-600); + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; +`; + +const TagDisplaySection = styled.div` + margin: 24px 0; +`; + +export { + SectionContainer, + ItemImageContainer, + ImageWrapper, + ItemDetailsContainer, + MainDetails, + SeeMoreButton, + ItemTitle, + ItemPrice, + Description, + SectionLabel, + TagDisplaySection, +}; \ No newline at end of file diff --git a/components/items/itemPage/ItemProfileSection.tsx b/components/items/itemPage/ItemProfileSection.tsx index f1321f09..4b4b3841 100644 --- a/components/items/itemPage/ItemProfileSection.tsx +++ b/components/items/itemPage/ItemProfileSection.tsx @@ -1,4 +1,3 @@ -import styled from "styled-components"; import { LineDivider } from "@/styles/CommonStyles"; import TagDisplay from "./TagDisplay"; import LikeButton from "./LikeButton"; @@ -6,108 +5,25 @@ import SeeMoreIcon from "@/public/images/icons/ic_kebab.svg"; import { Product } from "@/types/productTypes"; import Image from "next/image"; -const SectionContainer = styled.section` - display: flex; - flex-direction: column; - gap: 16px; - width: 100%; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - flex-direction: row; - } - - @media ${({ theme }) => theme.mediaQuery.desktop} { - gap: 24px; - } -`; - -const ItemImageContainer = styled.div` - width: 100%; - flex: 1; - aspect-ratio: 1/1; - border-radius: 12px; - overflow: hidden; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - width: 40%; - max-width: 486px; - } -`; - -const ImageWrapper = styled.div` - width: 100%; - height: 100%; - position: relative; -`; - -const ItemDetailsContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 1; - align-items: flex-start; -`; - -const MainDetails = styled.div` - width: 100%; - position: relative; -`; - -const SeeMoreButton = styled.button` - position: absolute; - right: 0; -`; - -const ItemTitle = styled.h1` - font-size: 16px; - font-weight: 600; - margin-bottom: 8px; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - font-size: 20px; - margin-bottom: 12px; - } - - @media ${({ theme }) => theme.mediaQuery.desktop} { - font-size: 24px; - margin-bottom: 16px; - } -`; - -const ItemPrice = styled.h2` - font-size: 24px; - font-weight: 600; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - font-size: 32px; - } - - @media ${({ theme }) => theme.mediaQuery.desktop} { - font-size: 40px; - } -`; - -const Description = styled.p` - font-size: 16px; - line-height: 140%; -`; - -const SectionLabel = styled.h3` - color: var(--gray-600); - font-size: 14px; - font-weight: 500; - margin-bottom: 8px; -`; - -const TagDisplaySection = styled.div` - margin: 24px 0; -`; - -interface ItemProfileSectionProps { +import { + SectionContainer, + ItemImageContainer, + ImageWrapper, + ItemDetailsContainer, + MainDetails, + SeeMoreButton, + ItemTitle, + ItemPrice, + Description, + SectionLabel, + TagDisplaySection, +} from "./ItemProfileSection.styles"; + +type ItemProfileSectionProps = { product: Product; } -const ItemProfileSection: React.FC = ({ product }) => { +const ItemProfileSection = ({ product }: ItemProfileSectionProps) => { return ( diff --git a/components/items/itemPage/LikeButton.styles.ts b/components/items/itemPage/LikeButton.styles.ts new file mode 100644 index 00000000..1a4f3895 --- /dev/null +++ b/components/items/itemPage/LikeButton.styles.ts @@ -0,0 +1,22 @@ +import styled from "styled-components"; + + +const PillButton = styled.button` + color: var(--gray-500); + font-size: 16px; + padding: 4px 12px; + border-radius: 999px; + border: 1px solid var(--gray-200); + + /* 버튼 hover 시 아이콘의 아웃라인과 채움색을 변경 */ + &:hover svg path { + fill: var(--red); + stroke: var(--red); + } +`; + +const ButtonContent = styled(FlexRowCentered)` + gap: 4px; +`; + +export { PillButton, ButtonContent }; \ No newline at end of file diff --git a/components/items/itemPage/LikeButton.tsx b/components/items/itemPage/LikeButton.tsx index 60b3930b..b936db53 100644 --- a/components/items/itemPage/LikeButton.tsx +++ b/components/items/itemPage/LikeButton.tsx @@ -1,37 +1,15 @@ -import styled from "styled-components"; import HeartSvg from "@/public/images/icons/ic_heart.svg"; import { FlexRowCentered } from "@/styles/CommonStyles"; import Icon from "@/components/ui/Icon"; +import { PillButton, ButtonContent } from "./LikeButton.styles"; -const PillButton = styled.button` - color: var(--gray-500); - font-size: 16px; - padding: 4px 12px; - border-radius: 999px; - border: 1px solid var(--gray-200); - - /* 버튼 hover 시 아이콘의 아웃라인과 채움색을 변경 */ - &:hover svg path { - fill: var(--red); - stroke: var(--red); - } -`; - -const ButtonContent = styled(FlexRowCentered)` - gap: 4px; -`; - -interface LikeButtonProps { +type LikeButtonProps = { productId: number; isFavorite: boolean; favoriteCount: number; } -const LikeButton: React.FC = ({ - productId, - isFavorite, - favoriteCount, -}) => { +const LikeButton = ({ productId, isFavorite, favoriteCount }: LikeButtonProps) => { return ( diff --git a/components/items/itemPage/TagDisplay.styles.ts b/components/items/itemPage/TagDisplay.styles.ts new file mode 100644 index 00000000..698bd906 --- /dev/null +++ b/components/items/itemPage/TagDisplay.styles.ts @@ -0,0 +1,17 @@ +import styled from "styled-components"; + +const TagsDisplaySection = styled.div` + display: flex; + gap: 8px; + flex-wrap: wrap; +`; + +const Tag = styled.div` + background-color: ${({ theme }) => theme.colors.gray[50]}; + color: ${({ theme }) => theme.colors.gray[800]}; + padding: 6px 16px; + border-radius: 999px; + font-size: 16px; +`; + +export { TagsDisplaySection, Tag }; \ No newline at end of file diff --git a/components/items/itemPage/TagDisplay.tsx b/components/items/itemPage/TagDisplay.tsx index 38f3f997..c91b7bbd 100644 --- a/components/items/itemPage/TagDisplay.tsx +++ b/components/items/itemPage/TagDisplay.tsx @@ -1,24 +1,10 @@ -import styled from "styled-components"; +import { TagsDisplaySection, Tag } from "./TagDisplay.styles" -const TagsDisplaySection = styled.div` - display: flex; - gap: 8px; - flex-wrap: wrap; -`; - -const Tag = styled.div` - background-color: ${({ theme }) => theme.colors.gray[50]}; - color: ${({ theme }) => theme.colors.gray[800]}; - padding: 6px 16px; - border-radius: 999px; - font-size: 16px; -`; - -interface TagDisplayProps { +type TagDisplayProps = { tags: string[]; } -const TagDisplay: React.FC = ({ tags }) => { +const TagDisplay = ({ tags }: TagDisplayProps) => { if (!tags || tags.length === 0) return null; return ( diff --git a/components/items/marketPage/AllItemsSection.styles.ts b/components/items/marketPage/AllItemsSection.styles.ts new file mode 100644 index 00000000..458a3f24 --- /dev/null +++ b/components/items/marketPage/AllItemsSection.styles.ts @@ -0,0 +1,23 @@ +import styled from "styled-components"; +import { StyledLink } from "@/styles/CommonStyles"; + +const AddItemLink = styled(StyledLink)``; + +const AllItemsCardSection = styled.div` + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 32px 8px; + + @media ${({ theme }) => theme.mediaQuery.desktop} { + grid-template-columns: repeat(5, 1fr); + grid-template-rows: repeat(2, auto); + gap: 40px 24px; + } +`; + +const PaginationBarWrapper = styled.div` + padding-top: 40px; + padding-bottom: 80px; +`; + +export { AddItemLink, AllItemsCardSection, PaginationBarWrapper }; \ No newline at end of file diff --git a/components/items/marketPage/AllItemsSection.tsx b/components/items/marketPage/AllItemsSection.tsx index 4bb2320b..affabcce 100644 --- a/components/items/marketPage/AllItemsSection.tsx +++ b/components/items/marketPage/AllItemsSection.tsx @@ -10,30 +10,16 @@ import { ProductSortOption, } from "@/types/productTypes"; import { MarketSectionTitle } from "@/styles/MarketStyles"; -import styled from "styled-components"; -import { SectionHeader, StyledLink } from "@/styles/CommonStyles"; +import { SectionHeader } from "@/styles/CommonStyles"; import useViewport from "@/hooks/useViewport"; import { useRouter } from "next/router"; import SearchBar from "@/components/ui/SearchBar"; -const AddItemLink = styled(StyledLink)``; - -const AllItemsCardSection = styled.div` - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 32px 8px; - - @media ${({ theme }) => theme.mediaQuery.desktop} { - grid-template-columns: repeat(5, 1fr); - grid-template-rows: repeat(2, auto); - gap: 40px 24px; - } -`; - -const PaginationBarWrapper = styled.div` - padding-top: 40px; - padding-bottom: 80px; -`; +import { + AllItemsCardSection, + AddItemLink, + PaginationBarWrapper, +} from "./AllItemsSection.styles"; /** * Calculates the appropriate page size for the given viewport width. diff --git a/components/items/marketPage/BestItemsSection.styles.ts b/components/items/marketPage/BestItemsSection.styles.ts new file mode 100644 index 00000000..9353b326 --- /dev/null +++ b/components/items/marketPage/BestItemsSection.styles.ts @@ -0,0 +1,27 @@ +import styled from "styled-components"; + +const BestItemsContainer = styled.div` + padding-top: 17px; + padding-bottom: 24px; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + margin-bottom: 40px; + } +`; + +const BestItemsCardSection = styled.div` + display: grid; + grid-template-columns: repeat(1, 1fr); + gap: 32px 8px; + + @media ${({ theme }) => theme.mediaQuery.tablet} { + grid-template-columns: repeat(2, 1fr); + gap: 24px; + } + + @media ${({ theme }) => theme.mediaQuery.desktop} { + grid-template-columns: repeat(4, 1fr); + } +`; + +export { BestItemsContainer, BestItemsCardSection }; \ No newline at end of file diff --git a/components/items/marketPage/BestItemsSection.tsx b/components/items/marketPage/BestItemsSection.tsx index c873f577..6bcab30b 100644 --- a/components/items/marketPage/BestItemsSection.tsx +++ b/components/items/marketPage/BestItemsSection.tsx @@ -3,33 +3,13 @@ import ItemCard from "./ItemCard"; import { getProducts } from "@/api/itemApi"; import LoadingSpinner from "@/components/ui/LoadingSpinner"; import { Product, ProductListResponse } from "@/types/productTypes"; -import styled from "styled-components"; import { MarketSectionTitle } from "@/styles/MarketStyles"; import useViewport from "@/hooks/useViewport"; -const BestItemsContainer = styled.div` - padding-top: 17px; - padding-bottom: 24px; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - margin-bottom: 40px; - } -`; - -const BestItemsCardSection = styled.div` - display: grid; - grid-template-columns: repeat(1, 1fr); - gap: 32px 8px; - - @media ${({ theme }) => theme.mediaQuery.tablet} { - grid-template-columns: repeat(2, 1fr); - gap: 24px; - } - - @media ${({ theme }) => theme.mediaQuery.desktop} { - grid-template-columns: repeat(4, 1fr); - } -`; +import { + BestItemsContainer, + BestItemsCardSection, +} from "./BestItemsSection.styles"; /** * Determines the appropriate page size for displaying product items based on the viewport width. diff --git a/components/items/marketPage/ItemCard.styles.ts b/components/items/marketPage/ItemCard.styles.ts new file mode 100644 index 00000000..755bc268 --- /dev/null +++ b/components/items/marketPage/ItemCard.styles.ts @@ -0,0 +1,46 @@ +import styled from "styled-components"; +import Link from "next/link"; + +const ItemCardContainer = styled(Link)` + color: var(--gray-800); + overflow: hidden; + cursor: pointer; +`; + +const ItemCardThumbnail = styled.img` + width: 100%; + height: auto; + object-fit: cover; + border-radius: 16px; + overflow: hidden; + aspect-ratio: 1; + margin-bottom: 16px; +`; + +const ItemSummary = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + flex-grow: 1; +`; + +const ItemName = styled.h2` + font-size: 16px; + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +const ItemPrice = styled.p` + font-size: 16px; + font-weight: bold; +`; + +export { + ItemCardContainer, + ItemCardThumbnail, + ItemSummary, + ItemName, + ItemPrice, +}; \ No newline at end of file diff --git a/components/items/marketPage/ItemCard.tsx b/components/items/marketPage/ItemCard.tsx index 7fcd39e1..acf660a1 100644 --- a/components/items/marketPage/ItemCard.tsx +++ b/components/items/marketPage/ItemCard.tsx @@ -1,49 +1,19 @@ import { Product } from "@/types/productTypes"; -import styled from "styled-components"; -import Link from "next/link"; import LikeCountDisplay from "@/components/ui/LikeCountDisplay"; -const ItemCardContainer = styled(Link)` - color: var(--gray-800); - overflow: hidden; - cursor: pointer; -`; +import { + ItemCardContainer, + ItemCardThumbnail, + ItemSummary, + ItemName, + ItemPrice, +} from "./ItemCard.styles"; -const ItemCardThumbnail = styled.img` - width: 100%; - height: auto; - object-fit: cover; - border-radius: 16px; - overflow: hidden; - aspect-ratio: 1; - margin-bottom: 16px; -`; - -const ItemSummary = styled.div` - display: flex; - flex-direction: column; - gap: 10px; - flex-grow: 1; -`; - -const ItemName = styled.h2` - font-size: 16px; - font-weight: 400; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const ItemPrice = styled.p` - font-size: 16px; - font-weight: bold; -`; - -interface ItemCardProps { +type ItemCardProps = { item: Product; } -const ItemCard: React.FC = ({ item }) => { +const ItemCard = ({ item }: ItemCardProps) => { return ( = ({ children }) => { +const Layout = ({ children }: LayoutProps) => { const router = useRouter(); const isAuthPage = router.pathname === "/login" || router.pathname === "/signup"; diff --git a/components/ui/DropdownMenu.styles.ts b/components/ui/DropdownMenu.styles.ts new file mode 100644 index 00000000..c935b445 --- /dev/null +++ b/components/ui/DropdownMenu.styles.ts @@ -0,0 +1,42 @@ +import styled from "styled-components"; + +const SortButtonWrapper = styled.div` + position: relative; +`; + +const SortDropdownTriggerButton = styled.button` + border: 1px solid var(--gray-200); + border-radius: 12px; + padding: 9px; + margin-left: 8px; +`; + +const DropdownMenuContainer = styled.div` + position: absolute; + top: 110%; + right: 0; + background: #fff; + border-radius: 8px; + border: 1px solid var(--gray-200); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 99; +`; + +const DropdownItem = styled.div` + padding: 12px 44px; + border-bottom: 1px solid var(--gray-200); + font-size: 16px; + color: var(--gray-800); + cursor: pointer; + + &:last-child { + border-bottom: none; + } +`; + +export { + SortButtonWrapper, + SortDropdownTriggerButton, + DropdownMenuContainer, + DropdownItem, +}; diff --git a/components/ui/DropdownMenu.tsx b/components/ui/DropdownMenu.tsx index 0070736c..86ba2498 100644 --- a/components/ui/DropdownMenu.tsx +++ b/components/ui/DropdownMenu.tsx @@ -1,50 +1,18 @@ import { useState } from "react"; -import styled from "styled-components"; import SortIcon from "@/public/images/icons/ic_sort.svg"; - -const SortButtonWrapper = styled.div` - position: relative; -`; - -const SortDropdownTriggerButton = styled.button` - border: 1px solid var(--gray-200); - border-radius: 12px; - padding: 9px; - margin-left: 8px; -`; - -const DropdownMenuContainer = styled.div` - position: absolute; - top: 110%; - right: 0; - background: #fff; - border-radius: 8px; - border: 1px solid var(--gray-200); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - z-index: 99; -`; - -const DropdownItem = styled.div` - padding: 12px 44px; - border-bottom: 1px solid var(--gray-200); - font-size: 16px; - color: var(--gray-800); - cursor: pointer; - - &:last-child { - border-bottom: none; - } -`; - -interface DropdownMenuProps { +import { + SortButtonWrapper, + SortDropdownTriggerButton, + DropdownMenuContainer, + DropdownItem, +} from "./DropdownMenu.styles" + +type DropdownMenuProps = { onSortSelection: (sortOption: any) => void; sortOptions: { key: string; label: string }[]; } -const DropdownMenu: React.FC = ({ - onSortSelection, - sortOptions, -}) => { +const DropdownMenu = ({ onSortSelection, sortOptions }: DropdownMenuProps) => { const [isDropdownVisible, setIsDropdownVisible] = useState(false); const toggleDropdown = () => { diff --git a/components/ui/EmptyState.styles.ts b/components/ui/EmptyState.styles.ts new file mode 100644 index 00000000..a821d63a --- /dev/null +++ b/components/ui/EmptyState.styles.ts @@ -0,0 +1,17 @@ +import styled from "styled-components"; + +const EmptyStateContainer = styled.div` + margin: 24px; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +`; + +const EmptyStateText = styled.p` + color: ${({ theme }) => theme.colors.gray[400]}; + font-size: 16px; + line-height: 24px; +`; + +export { EmptyStateContainer, EmptyStateText }; diff --git a/components/ui/EmptyState.tsx b/components/ui/EmptyState.tsx index ed74bf3a..02b0c007 100644 --- a/components/ui/EmptyState.tsx +++ b/components/ui/EmptyState.tsx @@ -1,19 +1,5 @@ -import styled from "styled-components"; import EmptyStateImage from "@/public/images/ui/empty-comments.svg"; - -const EmptyStateContainer = styled.div` - margin: 24px; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; -`; - -const EmptyStateText = styled.p` - color: ${({ theme }) => theme.colors.gray[400]}; - font-size: 16px; - line-height: 24px; -`; +import { EmptyStateContainer, EmptyStateText } from "./EmptyState.styles"; interface EmptyStateProps { text: string; diff --git a/components/ui/Icon.styles.ts b/components/ui/Icon.styles.ts new file mode 100644 index 00000000..966f4612 --- /dev/null +++ b/components/ui/Icon.styles.ts @@ -0,0 +1,26 @@ +import styled from "styled-components"; + +type IconWrapperProps = { + $size?: number; + $fillColor?: string; + $outlineColor?: string; +} + +const IconWrapper = styled.div` + display: inline-flex; + align-items: center; + justify-content: center; + + svg { + fill: ${({ $fillColor }) => $fillColor || "current"}; + width: ${({ $size }) => ($size ? `${$size}px` : "auto")}; + height: ${({ $size }) => ($size ? `${$size}px` : "auto")}; + } + + svg path { + stroke: ${({ $fillColor, $outlineColor }) => + $fillColor || $outlineColor || "currentColor"}; + } +`; + +export { IconWrapper }; diff --git a/components/ui/Icon.tsx b/components/ui/Icon.tsx index f021536d..802ebbe3 100644 --- a/components/ui/Icon.tsx +++ b/components/ui/Icon.tsx @@ -1,41 +1,18 @@ -import styled from "styled-components"; +import { IconWrapper } from "./Icon.styles"; -interface IconWrapperProps { - $size?: number; - $fillColor?: string; - $outlineColor?: string; -} - -const IconWrapper = styled.div` - display: inline-flex; - align-items: center; - justify-content: center; - - svg { - fill: ${({ $fillColor }) => $fillColor || "current"}; - width: ${({ $size }) => ($size ? `${$size}px` : "auto")}; - height: ${({ $size }) => ($size ? `${$size}px` : "auto")}; - } - - svg path { - stroke: ${({ $fillColor, $outlineColor }) => - $fillColor || $outlineColor || "currentColor"}; - } -`; - -interface IconProps { +type IconProps = { iconComponent: React.FunctionComponent>; size?: number; fillColor?: string; outlineColor?: string; } -const Icon: React.FC = ({ +const Icon = ({ iconComponent: IconComponent, size, fillColor = "currentColor", outlineColor = "currentColor", -}) => ( +}: IconProps) => ( diff --git a/components/ui/LikeCountDisplay.styles.ts b/components/ui/LikeCountDisplay.styles.ts new file mode 100644 index 00000000..7903a10f --- /dev/null +++ b/components/ui/LikeCountDisplay.styles.ts @@ -0,0 +1,13 @@ +import styled from "styled-components"; +import { FlexRowCentered } from "@/styles/CommonStyles"; + +const LikeCountWrapper = styled(FlexRowCentered)<{ + $fontSize: number; + $gap: number; +}>` + color: var(--gray-500); + font-size: ${({ $fontSize }) => `${$fontSize}px`}; + gap: ${({ $gap }) => `${$gap}px`}; +`; + +export { LikeCountWrapper }; diff --git a/components/ui/LikeCountDisplay.tsx b/components/ui/LikeCountDisplay.tsx index c01de4dd..22a162ea 100644 --- a/components/ui/LikeCountDisplay.tsx +++ b/components/ui/LikeCountDisplay.tsx @@ -1,18 +1,8 @@ import React from "react"; -import styled from "styled-components"; import HeartIcon from "@/public/images/icons/ic_heart.svg"; -import { FlexRowCentered } from "@/styles/CommonStyles"; +import { LikeCountWrapper } from "./LikeCountDisplay.styles"; -const LikeCountWrapper = styled(FlexRowCentered)<{ - $fontSize: number; - $gap: number; -}>` - color: var(--gray-500); - font-size: ${({ $fontSize }) => `${$fontSize}px`}; - gap: ${({ $gap }) => `${$gap}px`}; -`; - -interface LikeCountDisplayProps { +type LikeCountDisplayProps = { count: number; iconWidth?: number; fontSize?: number; @@ -20,13 +10,13 @@ interface LikeCountDisplayProps { className?: string; } -const LikeCountDisplay: React.FC = ({ +const LikeCountDisplay = ({ count, iconWidth = 16, fontSize = 16, gap = 4, className, -}) => { +}: LikeCountDisplayProps) => { const displayCount = count >= 10000 ? "9999+" : count.toString(); return ( diff --git a/components/ui/LoadingSpinner.styles.ts b/components/ui/LoadingSpinner.styles.ts new file mode 100644 index 00000000..2d0d145e --- /dev/null +++ b/components/ui/LoadingSpinner.styles.ts @@ -0,0 +1,26 @@ +import styled from "styled-components"; + +const MaskedBackground = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #fff; + z-index: 9998; +`; + +const SpinnerOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.2); + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +`; + +export { MaskedBackground, SpinnerOverlay }; \ No newline at end of file diff --git a/components/ui/LoadingSpinner.tsx b/components/ui/LoadingSpinner.tsx index da4f063e..e38a16bb 100644 --- a/components/ui/LoadingSpinner.tsx +++ b/components/ui/LoadingSpinner.tsx @@ -1,43 +1,20 @@ import { useEffect, useState } from "react"; -import styled from "styled-components"; import { PulseLoader } from "react-spinners"; +import { MaskedBackground, SpinnerOverlay } from "./LoadingSpinner.styles"; -const MaskedBackground = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: #fff; - z-index: 9998; -`; - -const SpinnerOverlay = styled.div` - position: fixed; - top: 0; - left: 0; - background: rgba(0, 0, 0, 0.2); - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - z-index: 9999; -`; - -interface LoadingSpinnerProps { +type LoadingSpinnerProps = { isLoading: boolean; size?: number; color?: string; minLoadTime?: number; } -const LoadingSpinner: React.FC = ({ +const LoadingSpinner = ({ isLoading, size = 20, color = "var(--blue)", minLoadTime = 500, -}) => { +}: LoadingSpinnerProps) => { const [isVisible, setIsVisible] = useState(isLoading); useEffect(() => { diff --git a/components/ui/PaginationBar.tsx b/components/ui/PaginationBar.tsx index 92255f6c..d0d69c01 100644 --- a/components/ui/PaginationBar.tsx +++ b/components/ui/PaginationBar.tsx @@ -26,17 +26,17 @@ const PaginationButton = styled.button<{ $isActive?: boolean }>` opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; `; -interface PaginationBarProps { +type PaginationBarProps = { totalPageNum: number; activePageNum: number; onPageChange: (pageNumber: number) => void; } -const PaginationBar: React.FC = ({ +const PaginationBar = ({ totalPageNum, activePageNum, onPageChange, -}) => { +}: PaginationBarProps) => { const maxVisiblePages = 5; let startPage: number; diff --git a/components/ui/SearchBar.styles.ts b/components/ui/SearchBar.styles.ts new file mode 100644 index 00000000..edf222a7 --- /dev/null +++ b/components/ui/SearchBar.styles.ts @@ -0,0 +1,27 @@ +import styled from "styled-components"; +import { FlexRowCentered } from "@/styles/CommonStyles"; + +const Container = styled(FlexRowCentered)` + background-color: var(--gray-100); + border-radius: 12px; + padding: 9px 16px; + flex: 1; +`; + +const SearchBarInput = styled.input` + border: none; + flex: 1; + background-color: inherit; + margin-left: 4px; + + &::placeholder { + color: var(--gray-400); + font-size: 16px; + } + + &:focus { + outline: none; + } +`; + +export { Container, SearchBarInput }; diff --git a/components/ui/SearchBar.tsx b/components/ui/SearchBar.tsx index d74963c3..aab49305 100644 --- a/components/ui/SearchBar.tsx +++ b/components/ui/SearchBar.tsx @@ -1,41 +1,17 @@ -import styled from "styled-components"; -import { FlexRowCentered } from "@/styles/CommonStyles"; import SearchIcon from "@/public/images/icons/ic_search.svg"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import { Container, SearchBarInput } from "./SearchBar.styles"; -const Container = styled(FlexRowCentered)` - background-color: var(--gray-100); - border-radius: 12px; - padding: 9px 16px; - flex: 1; -`; - -const SearchBarInput = styled.input` - border: none; - flex: 1; - background-color: inherit; - margin-left: 4px; - - &::placeholder { - color: var(--gray-400); - font-size: 16px; - } - - &:focus { - outline: none; - } -`; - -interface SearchBarProps { +type SearchBarProps = { onSearch: (keyword: string) => void; placeholder?: string; } -const SearchBar: React.FC = ({ +const SearchBar = ({ onSearch, placeholder = "검색할 키워드를 입력해 주세요", -}) => { +}: SearchBarProps) => { const router = useRouter(); const [keyword, setKeyword] = useState(""); diff --git a/pages/boards/index.styles.ts b/pages/boards/index.styles.ts new file mode 100644 index 00000000..ae838906 --- /dev/null +++ b/pages/boards/index.styles.ts @@ -0,0 +1,8 @@ +import styled from "styled-components"; +import { Container } from "@/styles/CommonStyles"; + +const PageContainer = styled(Container)` + gap: 40px; +`; + +export { PageContainer }; diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx index 5cd9f3b9..c9f69ab7 100644 --- a/pages/boards/index.tsx +++ b/pages/boards/index.tsx @@ -1,14 +1,9 @@ import React from "react"; -import styled from "styled-components"; -import { Container } from "@/styles/CommonStyles"; import BestArticlesSection from "../../components/boards/BestArticlesSection"; import AllArticlesSection from "../../components/boards/AllArticlesSection"; import { GetStaticProps } from "next"; import { Article, ArticleListResponse } from "@/types/articleTypes"; - -const PageContainer = styled(Container)` - gap: 40px; -`; +import { PageContainer } from "./index.styles"; export const getStaticProps: GetStaticProps = async () => { const response = await fetch( diff --git a/pages/items/[id].styles.ts b/pages/items/[id].styles.ts new file mode 100644 index 00000000..2c7f0ef5 --- /dev/null +++ b/pages/items/[id].styles.ts @@ -0,0 +1,13 @@ +import styled from "styled-components"; +import { StyledLink } from "@/styles/CommonStyles"; + +const BackToMarketPageLink = styled(StyledLink)` + display: flex; + align-items: center; + gap: 10px; + font-size: 18px; + font-weight: 600; + margin: 0 auto; +`; + +export { BackToMarketPageLink }; diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx index 1fce5059..448e601e 100644 --- a/pages/items/[id].tsx +++ b/pages/items/[id].tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; -import styled from "styled-components"; -import { Container, LineDivider, StyledLink } from "@/styles/CommonStyles"; +import { Container, LineDivider } from "@/styles/CommonStyles"; import { getProductDetail } from "@/api/itemApi"; import ItemProfileSection from "../../components/items/itemPage/ItemProfileSection"; import ItemCommentSection from "../../components/items/itemPage/ItemCommentSection"; @@ -8,17 +7,9 @@ import BackIcon from "@/public/images/icons/ic_back.svg"; import LoadingSpinner from "@/components/ui/LoadingSpinner"; import { Product } from "@/types/productTypes"; import { useRouter } from "next/router"; +import { BackToMarketPageLink } from "./[id].styles"; -const BackToMarketPageLink = styled(StyledLink)` - display: flex; - align-items: center; - gap: 10px; - font-size: 18px; - font-weight: 600; - margin: 0 auto; -`; - -const ItemPage: React.FC = () => { +const ItemPage = () => { const [product, setProduct] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); diff --git a/types/articleTypes.ts b/types/articleTypes.ts index 7618595f..bd4fe119 100644 --- a/types/articleTypes.ts +++ b/types/articleTypes.ts @@ -1,4 +1,4 @@ -export interface Article { +export type Article = { updatedAt: Date; createdAt: Date; likeCount: number; @@ -9,7 +9,7 @@ export interface Article { id: number; } -export interface ArticleListResponse { +export type ArticleListResponse = { totalCount: number; list: Article[]; }