-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #275 from leesh7048/Next-이승현-sprint9
[이승현] Sprint9
- Loading branch information
Showing
24 changed files
with
696 additions
and
496 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.