Skip to content

Commit

Permalink
feat: implement BoardList component
Browse files Browse the repository at this point in the history
  • Loading branch information
najitwo committed Oct 26, 2024
1 parent cd154cb commit b5ba32b
Show file tree
Hide file tree
Showing 22 changed files with 555 additions and 9 deletions.
18 changes: 16 additions & 2 deletions components/boards/ArticleImage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import { useState } from "react";
import Image from "next/image";
import Container from "../layout/Container";
import styles from "./ArticleImage.module.css";
import defaultImg from "@/public/img_default.svg";

interface ImageProps {
src: string;
src: string | null;
alt: string;
}

const ArticleImage = ({ src, alt }: ImageProps) => {
const [imageSrc, setImageSrc] = useState(src ?? defaultImg);

const handleImageError = () => {
setImageSrc(defaultImg);
};

return (
<Container className={styles.container}>
<div className={styles.wrapper}>
<Image fill src={src} alt={alt} className={styles.image} />
<Image
fill
src={imageSrc}
alt={alt}
className={styles.image}
onError={handleImageError}
/>
</div>
</Container>
);
Expand Down
4 changes: 0 additions & 4 deletions components/boards/BestBoard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,9 @@
color: var(--gray400);
}

@media screen and (min-width: 768px) {
}

@media screen and (min-width: 1200px) {
.content {
font-size: 1.25rem;
font-weight: 600;
line-height: 2rem;
margin-bottom: 18px;
}
Expand Down
9 changes: 8 additions & 1 deletion components/boards/BestBoards.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.wrapper {
padding-bottom: 24px;
}

.wrapper h2 {
font-size: 1.125rem;
font-weight: 700;
Expand All @@ -9,7 +13,6 @@
.container {
display: flex;
gap: 16px;
/* justify-content: space-between; */
}

@media screen and (min-width: 768px) {
Expand All @@ -21,6 +24,10 @@
}

@media screen and (min-width: 1200px) {
.wrapper {
padding-bottom: 40px;
}

.container {
gap: 24px;
}
Expand Down
56 changes: 56 additions & 0 deletions components/boards/Board.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.container {
background-color: #fcfcfc;
border-bottom: 1px solid var(--gray200);
padding-bottom: 24px;
}

.content {
display: flex;
justify-content: space-between;
font-size: 1.125rem;
font-weight: 600;
line-height: 1.625rem;
color: var(--gray800);
}

.info {
display: flex;
justify-content: space-between;
}

.authorInfo {
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5rem;
}

.authorInfo img {
width: 24px;
height: 24px;
}

.authorInfo span {
color: var(--gray600);
}

.authorInfo time {
color: var(--gray400);
}

.like img {
width: 24px;
height: 24px;
}

.like span {
font-size: 1rem;
line-height: 1.625rem;
}

@media screen and (min-width: 768px) {
.content {
font-size: 1.25rem;
line-height: 2rem;
margin-bottom: 18px;
}
}
27 changes: 27 additions & 0 deletions components/boards/Board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ArticleProps } from "@/types/articleTypes";
import Container from "../layout/Container";
import styles from "./Board.module.css";
import ArticleImage from "./ArticleImage";
import LikeCount from "../ui/LikeCount";
import AuthorInfo from "../ui/AuthorInfo";

const Board = ({ board }: { board: ArticleProps }) => {
return (
<Container className={styles.container}>
<div className={styles.content}>
{board.title}
<ArticleImage src={board.image} alt={`${board.id} 이미지`} />
</div>
<div className={styles.info}>
<AuthorInfo
className={styles.authorInfo}
nickname={board.writer.nickname}
date={board.createdAt}
/>
<LikeCount className={styles.like} likeCount={board.likeCount} />
</div>
</Container>
);
};

export default Board;
112 changes: 112 additions & 0 deletions components/boards/BoardLIst.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
.wrapper h2 {
font-size: 1.125rem;
font-weight: 700;
line-height: 1.625rem;
color: var(--gray900);
}

.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}

.button {
padding: 11.5px 23px;
}

.toolbar {
display: flex;
align-items: center;
gap: 13px;
margin-bottom: 16px;
}

.dropdown {
order: 1;
user-select: none;
}

.dropdown > div {
display: flex;
justify-content: center;
align-items: center;
width: 42px;
height: 42px;
border-radius: 12px;
border: 1px solid var(--gray200);
}

.dropdown ul {
width: 130px;
height: 84px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.dropdown li {
text-align: center;
font-size: 1rem;
font-weight: 400;
line-height: 1.625rem;
color: var(--gray800);
padding: 9px 0 7px;
}

.dropdown li:first-child {
border-bottom: 1px solid var(--gray200);
}

.container {
display: flex;
flex-direction: column;
gap: 24px;
}

@media screen and (min-width: 768px) {
.wrapper h2 {
font-size: 1.25rem;
line-height: 1.5rem;
}

.header {
margin-bottom: 48px;
}

.toolbar {
gap: 6px;
margin-bottom: 40px;
}

.dropdown {
order: 0;
}

.dropdown > div {
justify-content: space-evenly;
width: 130px;
height: 42px;
}

.dropdown img {
content: url("/ic_arrow_down.svg");
}

.dropdown span {
display: inline-block;
}
}

@media screen and (min-width: 1200px) {
.header {
margin-bottom: 24px;
}

.toolbar {
gap: 16px;
margin-bottom: 24px;
}
}
61 changes: 61 additions & 0 deletions components/boards/BoardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState, useCallback, useEffect } from "react";
import Image from "next/image";
import Board from "./Board";
import Button from "../ui/Button";
import Dropdown, { DropdownOptions } from "../ui/Dropdown";
import SearchBar from "../ui/SearchBar";
import { fetchData } from "@/lib/fetchData";
import styles from "./BoardLIst.module.css";
import sortIcon from "@/public/ic_sort.svg";
import Container from "../layout/Container";
import { ArticleProps } from "@/types/articleTypes";

const BoardList = () => {
const [boards, setBoards] = useState([]);
const [order, setOrder] = useState("recent");
const BASE_URL = "https://panda-market-api.vercel.app/articles";
const options: DropdownOptions = {
recent: "최신순",
like: "인기순",
};

const handleLoad = useCallback(async () => {
const { list } = await fetchData(BASE_URL, {
query: {
orderBy: order,
},
});
setBoards(list);
}, [order]);

useEffect(() => {
handleLoad();
}, [handleLoad]);

return (
<section className={styles.wrapper}>
<div className={styles.header}>
<h2>게시글</h2>
<Button className={styles.button}>글쓰기</Button>
</div>
<div className={styles.toolbar}>
<SearchBar />
<Dropdown
className={styles.dropdown}
options={options}
onSelect={setOrder}
>
<span>{options[order]}</span>
<Image src={sortIcon} alt="드롭다운" />
</Dropdown>
</div>
<Container className={styles.container}>
{boards.map((board: ArticleProps) => (
<Board key={board.id} board={board} />
))}
</Container>
</section>
);
};

export default BoardList;
8 changes: 8 additions & 0 deletions components/layout/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export default function Container({
page ? styles.page : ""
} ${className}`;

if (page) {
return (
<main className={classNames} {...props}>
{children}
</main>
);
}

return (
<div className={classNames} {...props}>
{children}
Expand Down
19 changes: 19 additions & 0 deletions components/ui/AuthorInfo.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.authorInfo {
display: flex;
align-items: center;
gap: 8px;
}

.user {
display: flex;
align-items: center;
gap: 8px;
}

.nickname {
color: var(--gray600);
}

.date {
color: var(--gray400);
}
27 changes: 27 additions & 0 deletions components/ui/AuthorInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Image from "next/image";
import styles from "./AuthorInfo.module.css";
import profileIcon from "@/public/ic_profile.svg";
import { formatDate } from "@/lib/formatDate";

interface Props {
className?: string;
nickname: string;
image?: string;
date: string;
}

const AuthorInfo = ({ className, nickname, image, date }: Props) => {
return (
<div className={`${styles.authorInfo} ${className}`}>
<Image src={image ?? profileIcon} alt="프로필 아이콘" />
<div className={styles.user}>
<span className={styles.nickname}>{nickname}</span>
<time className={styles.date} dateTime={date}>
{formatDate(date)}
</time>
</div>
</div>
);
};

export default AuthorInfo;
Loading

0 comments on commit b5ba32b

Please sign in to comment.