-
Notifications
You must be signed in to change notification settings - Fork 79
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 #642 from YehaYoo/Next.js-유예하-sprint10
[유예하] sprint10
- Loading branch information
Showing
21 changed files
with
944 additions
and
150 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,83 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import debounce from "lodash-es/debounce"; | ||
import Articles, { ArticleListProps } from "./Articles"; | ||
import Dropdown from "./Dropdown"; | ||
import SearchBar from "./SearchBar"; | ||
import { getArticles } from "../lib/api"; | ||
import styles from "../styles/boards.module.css"; | ||
|
||
interface AllArticlesSectionProps { | ||
initialAllArticles: ArticleListProps[]; | ||
} | ||
|
||
const AllArticlesSection = ({ | ||
initialAllArticles, | ||
}: AllArticlesSectionProps) => { | ||
const [articles, setArticles] = | ||
useState<ArticleListProps[]>(initialAllArticles); | ||
const [loading, setLoading] = useState<boolean>(false); | ||
const [currentOrder, setCurrentOrder] = useState<"recent" | "like">("recent"); | ||
const [filteredArticles, setFilteredArticles] = | ||
useState<ArticleListProps[]>(initialAllArticles); | ||
|
||
const fetchArticles = async (order: "recent" | "like") => { | ||
setLoading(true); | ||
try { | ||
const data = await getArticles({ limit: 10, order }); | ||
setArticles(data); | ||
setFilteredArticles(data); | ||
} catch (error) { | ||
console.error("Failed to fetch articles", error); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
const handleSortChange = async (order: "recent" | "like") => { | ||
setCurrentOrder(order); | ||
await fetchArticles(order); | ||
}; | ||
|
||
useEffect(() => { | ||
if (initialAllArticles.length === 0) { | ||
fetchArticles(currentOrder); | ||
} | ||
}, [currentOrder]); | ||
|
||
const debouncedFetchArticles = debounce(async (searchTerm: string) => { | ||
const filtered = articles.filter((article) => | ||
article.title.toLowerCase().includes(searchTerm.toLowerCase()) | ||
); | ||
setFilteredArticles(filtered); | ||
}, 300); | ||
|
||
const handleSearch = (searchTerm: string) => { | ||
debouncedFetchArticles(searchTerm); | ||
}; | ||
|
||
return ( | ||
<div className={styles.articleSectionWrapper}> | ||
<section className={styles.articleSection}> | ||
<div className={styles.articleSectioMenu}> | ||
<p className={styles.articleSectionTitle}>게시글</p> | ||
<button className={styles.articleSectionButton}>글쓰기</button> | ||
</div> | ||
<div> | ||
<div className={styles.searchbarDropdownWrapper}> | ||
<SearchBar onSearch={handleSearch} /> | ||
<div> | ||
<Dropdown | ||
onSortByNewest={() => handleSortChange("recent")} | ||
onSortByBest={() => handleSortChange("like")} | ||
order={currentOrder} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<Articles articles={filteredArticles} /> | ||
</section> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AllArticlesSection; |
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
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
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,109 @@ | ||
import React, { useState } from "react"; | ||
import { postComment, getArticleComments } from "../lib/api"; | ||
import { isEmpty } from "lodash"; | ||
|
||
function formatRelativeDate(dateString: number) { | ||
const date = new Date(dateString); | ||
const currentDate = new Date(); | ||
const diffTime = Math.abs(currentDate.getTime() - date.getTime()); | ||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | ||
|
||
if (diffDays === 0) { | ||
return "오늘"; | ||
} else if (diffDays === 1) { | ||
return "어제"; | ||
} else { | ||
return `${diffDays}일 전`; | ||
} | ||
} | ||
|
||
interface Comment { | ||
id: number; | ||
content: string; | ||
updatedAt: number; | ||
writer: { | ||
image: string; | ||
nickname: string; | ||
}; | ||
} | ||
|
||
interface CommentProps { | ||
comments: Comment[]; | ||
numericArticleID: number; | ||
setComments: React.Dispatch<React.SetStateAction<Comment[]>>; | ||
} | ||
|
||
function CommentSection({ | ||
comments, | ||
numericArticleID, | ||
setComments, | ||
}: CommentProps) { | ||
const [commentText, setCommentText] = useState<string>(""); | ||
|
||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
|
||
if (!commentText.trim()) { | ||
return; | ||
} | ||
|
||
const accessToken = | ||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NjksInNjb3BlIjoicmVmcmVzaCIsImlhdCI6MTcxNzY1ODMzMCwiZXhwIjoxNzE4MjYzMTMwLCJpc3MiOiJzcC1wYW5kYS1tYXJrZXQifQ.D-jcuvSNCR65Qj1sbenHsi_EkABnh8xw8fGb49mD4AA"; | ||
|
||
try { | ||
await postComment(numericArticleID, commentText, accessToken); | ||
setCommentText(""); | ||
const updatedComments = await getArticleComments(numericArticleID); | ||
setComments(updatedComments); | ||
} catch (error) { | ||
console.error("Failed to post comment:", error); | ||
} | ||
}; | ||
|
||
return ( | ||
<section className="commentSection"> | ||
<p className="comment__title">댓글 달기</p> | ||
<form className="commentInput" onSubmit={handleSubmit}> | ||
<textarea | ||
className="commentInput__textarea" | ||
placeholder="댓글을 입력해주세요" | ||
value={commentText} | ||
onChange={(e) => setCommentText(e.target.value)} | ||
/> | ||
<button className="commentInput__Button" disabled={!commentText.trim()}> | ||
등록 | ||
</button> | ||
</form> | ||
<div className="commentContainer"> | ||
{isEmpty(comments) ? ( | ||
<div className="commentEmpty"> | ||
<p className="commentEmptyMessage"> | ||
아직 댓글이 없어요, 지금 댓글을 달아보세요! | ||
</p> | ||
</div> | ||
) : ( | ||
comments.map((comment) => ( | ||
<div className="comment" key={comment.id}> | ||
<button className="commentKebab"></button> | ||
<div> | ||
<p className="commentContent">{comment.content}</p> | ||
</div> | ||
<div className="commentInfo"> | ||
<div className="commentInfoItems"> | ||
<p className="commentInfoNickname"> | ||
{comment.writer.nickname} | ||
</p> | ||
<p className="commentInfoUpdateAt"> | ||
{formatRelativeDate(comment.updatedAt)} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
)) | ||
)} | ||
</div> | ||
</section> | ||
); | ||
} | ||
|
||
export default CommentSection; |
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
Oops, something went wrong.