Skip to content

[최윤석] Sprint9 #708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: Next.js-최윤석
Choose a base branch
from
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
/node_modules
/.pnp
.pnp.js
<<<<<<< HEAD
.yarn/install-state.gz
=======
>>>>>>> upstream/Next.js-최윤석

# testing
/coverage
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ bun dev

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.


The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

Expand Down
33 changes: 33 additions & 0 deletions components/BestPost.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styles from "@/components/BestPost.module.css";
import { useEffect, useState } from "react";
import axios from "@/lib/axios";
import BestPostArticles from "./BestPostArticles";

export default function BestPost() {
const [bestArticles, setBestArticles] = useState([]);

async function getBestArticles() {
Copy link
Collaborator

@hoody-jellybean hoody-jellybean Jun 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아티클을 조회하는 API 함수를 만들어 분리하고, 이를 가져와서 사용하시는게 좋을 것 같습니다~!
(해당 함수는 API 요청만 처리하면 좋을 것 같아요.)

// 아래 분리하고 이를 가져와서 사용합니다. 
async function getBestArticles(params) {
  // 오류 처리는 필요한 곳에 위임할 수 있도록, 꼭 필요한 경우가 아니면 하지 않습니다.
  const res = await axios.get(`articles?page=${params.page}&pageSize=3&orderBy=${params.orderBy}`);
  return res.data.list ?? [];
}

try {
const res = await axios.get("articles?page=1&pageSize=3&orderBy=like");
const nextArticles = res.data.list ?? [];
setBestArticles(nextArticles);
} catch (error) {
console.error(error);
}
}

useEffect(() => {
getBestArticles();
Copy link
Collaborator

@hoody-jellybean hoody-jellybean Jun 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

응답의 결과로 데이터가 업데이트된 경우에만 고려되고 있는데요.
로딩 및 오류 처리가 가능하도록 다음 상태도 같이 관리하시면 좋을 것 같네요~!

  • isLoading: 데이터 패칭 여부
  • isError: 오류 여부

Copy link
Collaborator

@hoody-jellybean hoody-jellybean Jun 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이러한 기능을 할 수 있는 커스텀 훅을 만들어보는 것도 좋은 것 같습니다.
아래는 사용하는 예시입니다~!
(간단한 예제로 Infinite 케이스는 조금 더 복잡합니다~!)

const { isLoading, isError, data } = useAsync(
  () => getBestArticles(), // fetch 함수
  [] // 의존성 배열
)

}, []);

return (
<section className={styles.bestPost}>
<h1 className={styles.bestPostTitle}>베스트 게시글</h1>
<ul className={styles.bestItems}>
{bestArticles.map((item) => (
<BestPostArticles key={item.id} {...item} />
))}
</ul>
</section>
);
}
27 changes: 27 additions & 0 deletions components/BestPost.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.bestPost {
width: 1200px;
height: 217px;

display: flex;
flex-direction: column;
justify-content: space-between;
}

.bestPostTitle {
font-size: 20px;
font-weight: 700;
line-height: 23.87px;

color: #111827;
}

.bestItems {
width: 100%;
height: 169px;

display: grid;
grid-template-rows: 169px;
grid-template-columns: repeat(3, 384px);

gap: 24px;
}
53 changes: 53 additions & 0 deletions components/BestPostArticles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import styles from "@/components/BestPostArticles.module.css";
import Image from "next/image";
import medal from "@/public/ic_medal.png";
import heartIcon from "@/public/ic_heart.png";

export default function BestPostArticles({
title,
image,
likeCount,
updatedAt,
writer,
}) {
return (
<li className={styles.bestItem}>
<div className={styles.bestItemWrap}>
<div className={styles.badge}>
<div className={styles.badgeWrap}>
<Image src={medal} width={16} height={16} alt='메달 아이콘' />
<span>Best</span>
</div>
</div>
<div className={styles.bestUserContents}>
<h1 className={styles.bestTitle}>{title}</h1>
{image ? (
<div className={styles.userProductImg}>
<Image
className={styles.productImg}
width={48}
height={44.57}
src={image}
alt='상품 이미지'
/>
</div>
) : null}
</div>
<div className={styles.bestUserInfoWrap}>
<div className={styles.bestUserInfo}>
<span>{writer.nickname}</span>
<div className={styles.bestUserCount}>
<Image src={heartIcon} width={16} height={16} alt='하트 아이콘' />
<span className={styles.likeCount}>{likeCount}</span>
</div>
</div>
<div className={styles.bestUserDate}>
<span>{new Date(updatedAt).getFullYear()}</span>
<span>. {new Date(updatedAt).getMonth() + 1}</span>
<span>. {new Date(updatedAt).getDay()}</span>
</div>
</div>
</div>
</li>
);
}
122 changes: 122 additions & 0 deletions components/BestPostArticles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
.bestItem {
width: 384px;
height: 169px;

display: flex;
justify-content: center;
align-items: flex-start;

background-color: #f9fafb;
}

.bestItemWrap {
width: 336px;
height: 153px;

display: flex;
flex-direction: column;
justify-content: space-between;
}

.badge {
width: 102px;
height: 30px;

display: flex;
justify-content: center;

background-color: #3692ff;
border-radius: 0 0 32px 32px;
}

.badgeWrap {
width: 54px;
height: 24px;

display: flex;
align-items: center;
gap: 4px;
}

.badgeWrap span {
font-size: 16px;
font-weight: 600;
line-height: 24px;

color: #fff;
}

.bestUserContents {
width: 100%;

display: flex;
justify-content: space-between;
}

.bestTitle {
display: inline;

font-size: 20px;
font-weight: 600;
line-height: 23.87px;

color: #1f2937;
}

.userProductImg {
width: 71px;
height: 71px;

position: relative;

display: flex;
justify-content: center;
align-items: center;

border: 1px solid #e5e7eb;
}

.productImg {
width: 100%;
height: auto;
}

.bestUserInfoWrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
}

.bestUserInfo {
width: auto;
height: 17px;

display: flex;
align-items: flex-end;
gap: 8px;

font-size: 14px;
font-weight: 400;
line-height: 16.71px;

color: #4b5563;
}

.bestUserCount {
display: flex;
align-items: center;
gap: 4px;
}

.likeCount {
color: #6b7280;
}

.bestUserDate {
font-size: 14px;
font-weight: 400;
line-height: 16.71px;

color: #4b5563;
}
31 changes: 31 additions & 0 deletions components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Link from "next/link";
import Image from "next/image";
import styles from "./Header.module.css";
import headerLogo from "../public/header-logo.png";
import signImg from "../public/sign-img.png";

export default function Header() {
return (
<header className={styles.header}>
<nav className={styles.nav}>
<ul className={styles.menus}>
<li className={styles.title}>
<Image src={headerLogo} width={40} height={40} alt='로고' />
<h1>판다마켓</h1>
</li>
<li className={styles.links}>
<Link className={styles.link} href='/boards'>
자유게시판
</Link>
<Link className={styles.link} href='/'>
중고마켓
</Link>
</li>
</ul>
<div>
<Image src={signImg} width={40} height={40} alt='로그인 이미지' />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<img/> 태그의 alt 속성은 스크린 리더 사용자나 해당 이미지를 볼 수 없는 사람에게 제공되는 대체 텍스트입니다~!
유의미한 텍스트를 넣어주시는게 좋아요. (이 경우는 로그인하기 등이 될 수 있을 것 같네요.)

</div>
</nav>
</header>
);
}
62 changes: 62 additions & 0 deletions components/Header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.header {
width: 100%;
height: 70px;

padding: 0 200px;
display: flex;
justify-content: center;
align-items: center;

border-bottom: 1px solid #dfdfdf;
background-color: #fff;
}

.nav {
width: 100%;
height: 100%;

display: flex;
justify-content: space-between;
align-items: center;
}

.menus {
display: flex;
align-items: center;
gap: 32px;
}

.title {
width: 153px;

display: flex;
justify-content: space-between;
align-items: center;
}

.title h1 {
font-size: 25.63px;
font-weight: 700;
line-height: 34.6px;

color: #3692ff;
}

.links {
width: 218px;
display: flex;
}

.link {
flex-grow: 1;

font-size: 18px;
font-weight: 700;
line-height: 21.48px;

color: #4b5563;
}

.link:nth-child(1) {
color: #3692ff;
}
Loading