Skip to content
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

[이승현] Sprint9 #275

111 changes: 111 additions & 0 deletions components/boards/AllArticles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
.articleCard {
margin-top: 1rem;
background-color: var(--gray-50);
border-bottom: 1px solid var(--gray-200);
padding-bottom: 1.25rem;
}
.articleHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 2.5rem;
}
.articleTitle {
font-weight: 700;
font-size: 1.25rem;
line-height: 2rem;
}
.writeButton {
background-color: var(--blue);
color: #fff;
padding: 0.625rem 1.43rem;
border-radius: 0.5rem;
}
.articleSearch {
display: flex;
align-items: center;
gap: 1.25rem;
margin: 2rem 0;
}
.articleInput {
flex: 1;
border: none;
background-color: var(--gray-100);
}

.articleThumbnail {
background-color: #fff;
border: 1px solid var(--gray-200);
width: 4.5rem;
height: 4.5rem;
border-radius: 0.5rem;
padding: 0.75rem;
}
.imageWrapper {
width: 100%;
height: 100%;
position: relative;
}
.articleCardContents {
display: flex;
}
.articleCardTitle {
flex: 1;
font-size: 1.25rem;
font-weight: 600;
line-height: 2rem;
}
.articleCardInfo {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.625rem;
}
.articleCardInfoContents {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--gray-600);
font-size: 0.875rem;
}
.articleCardLikeCount {
font-size: 1rem;
font-weight: 400;
line-height: 1.6rem;
color: var(--gray-500);
display: flex;
gap: 4px;
align-items: center;
}
.articleInputBox {
background-color: var(--gray-100);
border-radius: 0.5rem;
padding: 0.7rem 0.6rem;
display: flex;
align-items: center;
color: var(--gray-400);
font-weight: 400;
gap: 0.5rem;
flex: 1;
}
.articleInput {
border: none;
background-color: var(--gray-100);
font-size: 1rem;
color: #000;
outline: none;
}
.articleInput::placeholder {
color: var(--gray-400);
font-weight: 400;
}
.orderSelect {
padding: 0.5rem;
outline: none;
border: 1px solid var(--gray-200);
border-radius: 0.7rem;
padding: 0.5rem 1rem;
font-weight: 400;
font-size: 1rem;
line-height: 1.6rem;
}
134 changes: 134 additions & 0 deletions components/boards/AllArticles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import axios from "@/lib/axios";
import Image from "next/image";
import { Article, ArticlesResponse } from "@/types/types";
import React, { ChangeEvent, useEffect, useState } from "react";
import profileIcon from "@/public/images/icons/ic_profile.svg";
import heartIcon from "@/public/images/icons/ic_heart.svg";
import searchIcon from "@/public/images/icons/ic_search.svg";

import styles from "./AllArticles.module.css";
import { useRouter } from "next/router";
import Link from "next/link";

const AllArticles = () => {
const [articles, setArticles] = useState<Article[]>([]);
const [order, setOrder] = useState<string>("recent");
const [searchKeyword, setSearchKeyword] = useState<string>("");

const router = useRouter();
const keyword = (router.query.keyword as string) || "";

useEffect(() => {
const getAllArticles = async () => {
let params = `/articles?page=1&pageSize=10&orderBy=${order}`;
if (keyword.trim()) {
params += `&keyword=${encodeURIComponent(keyword)}`;
}
const res = await axios.get<ArticlesResponse>(params);
const articles = res.data;
setArticles(articles.list);
};
Comment on lines +22 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

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

fetch가 무조건 성공한다는 가정으로 만들어진 코드같아요

실패의 경우엔 어떻게 핸들링 할지 try,catch 구문으로 에러 핸들링 코드도 적용해보시면 좋을것같아요!


getAllArticles();
}, [order, keyword]);

useEffect(() => {
setSearchKeyword(keyword);
}, [keyword]);

const dateFormat = (date: Date) => {
const newDate = new Date(date);
const formatDate = `${newDate.getFullYear()}.${String(
newDate.getMonth() + 1
).padStart(2, "0")}.${String(newDate.getDate()).padStart(2, "0")}`;
return formatDate;
};

const handleOrderChange = (e: ChangeEvent<HTMLSelectElement>) => {
setOrder(e.target.value);
};

const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchKeyword(e.target.value);
};
const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const query = { ...router.query };
if (e.key === "Enter") {
if (searchKeyword.trim()) {
query.keyword = searchKeyword;
} else {
delete query.keyword;
}

router.replace({
pathname: router.pathname,
query: query,
});
}
};
Comment on lines +54 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const query = { ...router.query };
if (e.key === "Enter") {
if (searchKeyword.trim()) {
query.keyword = searchKeyword;
} else {
delete query.keyword;
}
router.replace({
pathname: router.pathname,
query: query,
});
}
};
const updateRouterQuery = (query)=>{
router.replace({
pathname: router.pathname,
query: query,
});
}
const handleSearchKeyword = (e: React.KeyboardEvent<HTMLInputElement>) => {
const query = { ...router.query };
if (e.key === "Enter") {
if (searchKeyword.trim()) {
query.keyword = searchKeyword;
} else {
delete query.keyword;
}
}
updateRouterQuery(query)
};
  • 함수 하나에 2가지 일을 하고 있어서 2개로 분리하는건 어떨까요?
  • 분리하면 검색어를 업데이트 하는 함수, 그리고 라우터 쿼리를 업데이트 하는 함수 이렇게 2개로 나눌 수 있을것같아요.


return (
<>
<div className={styles.articleHeader}>
<h1 className={styles.articleTitle}>게시글</h1>
<button className={styles.writeButton}>글쓰기</button>
</div>
<div className={styles.articleSearch}>
<div className={styles.articleInputBox}>
<Image src={searchIcon} alt="searchIcon" />
<input
type="text"
className={styles.articleInput}
placeholder="검색할 상품을 입력해주세요"
value={searchKeyword}
onChange={handleSearchChange}
onKeyDown={handleSearchKeyDown}
/>
</div>
<select
className={styles.orderSelect}
name="order"
onChange={handleOrderChange}
>
<option value="recent">최신순</option>
<option value="like">좋아요순</option>
</select>
</div>
<div className={styles.articleList}>
{articles.map((article) => {
return (
<Link href={`/boards/${article.id}`} key={article.id}>
<div className={styles.articleCard}>
<div className={styles.articleCardContents}>
<p className={styles.articleCardTitle}>{article.title}</p>
<div className={styles.articleThumbnail}>
<div className={styles.imageWrapper}>
<Image
fill
src={article.image}
alt={`${article.id}번 게시글 이미지`}
style={{ objectFit: "contain" }}
/>
</div>
</div>
</div>
<div className={styles.articleCardInfo}>
<div className={styles.articleCardInfoContents}>
<Image src={profileIcon} alt="profileIcon" />
<span>{article.writer.nickname}</span>
<span>{dateFormat(article.createdAt)}</span>
</div>
<div className={styles.articleCardLikeCount}>
<Image src={heartIcon} alt="heartIcon" />
<span>{article.likeCount}</span>
</div>
</div>
</div>
</Link>
);
})}
</div>
</>
);
};
export default AllArticles;
71 changes: 71 additions & 0 deletions components/boards/BestArticles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.articleTitle {
font-weight: 700;
font-size: 20px;
line-height: 23.87px;
}
.articleSection {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.articleCard {
background-color: var(--gray-50);
border-radius: 8px;
padding: 0 24px 16px 24px;
}
.bestMark {
display: flex;
align-items: center;
background-color: var(--blue);
border-radius: 0 0 1rem 1rem;
font-size: 1rem;
font-weight: 600;
color: #fff;
gap: 4px;
padding: 6px 24px 8px 24px;
margin-left: 24px;
display: inline-flex;
}
.articleContent {
display: flex;
gap: 8px;
margin: 10px 0;
}
.articleTitle {
font-weight: 600;
font-size: 20px;
line-height: 32px;
flex: 1;
}
.articleThumbnail {
background-color: #fff;
border: 1px solid var(--gray-200);
width: 72px;
height: 72px;
border-radius: 8px;
padding: 12px;
}
.imageWrapper {
width: 100%;
height: 100%;
position: relative;
}
.articleInfo {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 400;
font-size: 14px;
line-height: 24px;
color: var(--gray-500);
}
.articleInfoContent {
display: flex;
align-items: center;
gap: 8px;
}
.articleLike {
display: flex;
align-items: center;
gap: 4px;
}
78 changes: 78 additions & 0 deletions components/boards/BestArticles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import axios from "@/lib/axios";
import Image from "next/image";
import { Article, ArticlesResponse } from "@/types/types";
import { useEffect, useState } from "react";
import medalIcon from "@/public/images/icons/ic_medal.svg";
import heartIcon from "@/public/images/icons/ic_heart.svg";
import styles from "./BestArticles.module.css";
import Link from "next/link";

const BestArticles = () => {
const [articles, setArticles] = useState<Article[]>([]);
const getBestArticles = async () => {
const res = await axios.get<ArticlesResponse>(
`/articles?page=1&pageSize=3&orderBy=like`
);
const articles = res.data;
setArticles(articles.list);
};
useEffect(() => {
getBestArticles();
}, []);
const dateFormat = (date: Date) => {
const newDate = new Date(date);
const formatDate = `${newDate.getFullYear()}.${String(
newDate.getMonth() + 1
).padStart(2, "0")}.${String(newDate.getDate()).padStart(2, "0")}`;
return formatDate;
};
Comment on lines +22 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

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

AllArticles에도 같은 함수가 있는것같아요.
만들어져 있는 함수를 재사용해보는건 어떨까요? :-)


return (
<>
<h1 className={styles.articleTitle}>베스트 게시글</h1>
<div className={styles.articleSection}>
{articles.map((article) => {
return (
<Link href={`/boards/${article.id}`} key={article.id}>
<div className={styles.articleCard}>
<div className={styles.bestMark}>
<Image src={medalIcon} alt="medalIcon" />
Best
</div>
<div className={styles.articleContent}>
<p className={styles.articleTitle}>{article.title}</p>
<div className={styles.articleThumbnail}>
<div className={styles.imageWrapper}>
<Image
fill
src={article.image}
alt={`${article.id}번 게시글 이미지`}
style={{ objectFit: "contain" }}
/>
</div>
</div>
</div>
<div className={styles.articleInfo}>
<div className={styles.articleInfoContent}>
<span>{article.writer.nickname}</span>
<div className={styles.articleLike}>
<Image src={heartIcon} alt="heart-icon" />
{article.likeCount > 9999
? 9999 + "+"
: article.likeCount}
Comment on lines +60 to +62
Copy link
Collaborator

Choose a reason for hiding this comment

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

9999 같은 숫자는 의미가 불분명해서

const MAX_COUNT = 9999

처럼 변수에 담아주면 의미 파악하기가 더 쉽습니다 :-)

</div>
</div>
<div className={styles.articleTimestamp}>
{dateFormat(article.createdAt)}
</div>
</div>
</div>
</Link>
);
})}
</div>
</>
);
};

export default BestArticles;
Loading
Loading