Skip to content

Commit

Permalink
Merge pull request #275 from leesh7048/Next-이승현-sprint9
Browse files Browse the repository at this point in the history
[이승현] Sprint9
  • Loading branch information
201steve authored Aug 11, 2024
2 parents 8d163dd + 141906f commit 0c093c0
Show file tree
Hide file tree
Showing 24 changed files with 696 additions and 496 deletions.
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);
};

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,
});
}
};

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;
};

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}
</div>
</div>
<div className={styles.articleTimestamp}>
{dateFormat(article.createdAt)}
</div>
</div>
</div>
</Link>
);
})}
</div>
</>
);
};

export default BestArticles;
Loading

0 comments on commit 0c093c0

Please sign in to comment.