Skip to content

Commit

Permalink
Merge pull request #642 from YehaYoo/Next.js-유예하-sprint10
Browse files Browse the repository at this point in the history
[유예하] sprint10
  • Loading branch information
kiJu2 authored Jun 10, 2024
2 parents 54628b5 + 4f20359 commit 853d6e8
Show file tree
Hide file tree
Showing 21 changed files with 944 additions and 150 deletions.
83 changes: 83 additions & 0 deletions components/AllArticlesSection.tsx
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;
12 changes: 2 additions & 10 deletions components/Articles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Link from "next/link";
import React from "react";
import formatDate from "../utils/formatData";
import styles from "./Articles.module.css";
import Image from "next/image";

Expand All @@ -17,15 +18,6 @@ export interface ArticleListProps {
createdAt: string;
}

function formatDate(dateString: string) {
const date = new Date(dateString);
return date.toLocaleDateString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}

function ArticleList({
id,
title,
Expand All @@ -36,7 +28,7 @@ function ArticleList({
}: ArticleListProps) {
const formattedDate = formatDate(createdAt);
return (
<Link className={styles.Link} href={`/items/${id}`}>
<Link className={styles.Link} href={`/addboard/${id}`}>
<section className={styles.articlesList}>
<div className={styles.articlesListTopItems}>
<p className={styles.articlesListTitle}>{title}</p>
Expand Down
81 changes: 61 additions & 20 deletions components/BestArticlesSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Link from "next/link";
import React from "react";
import React, { useEffect, useState } from "react";
import styles from "./BestArticlesSection.module.css";
import { getBestArticles } from "../lib/api";
import Image from "next/image";
import formatDate from "../utils/formatData";
import useResizeHandler from "./useResizeHandler";

export interface ArticleListProps {
id: number;
Expand All @@ -17,15 +20,6 @@ export interface ArticleListProps {
createdAt: string;
}

function formatDate(dateString: string) {
const date = new Date(dateString);
return date.toLocaleDateString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}

function BestArticlesCard({
id,
title,
Expand Down Expand Up @@ -76,19 +70,66 @@ function BestArticlesCard({
);
}

interface ArticlesProps {
articles: ArticleListProps[];
function getBestArticlesLimit() {
if (typeof window !== "undefined") {
const width = window.innerWidth;
if (width < 768) {
return 1;
} else if (width < 1280) {
return 2;
} else {
return 3;
}
}
return 3;
}

function BestArticlesSection({ articles }: ArticlesProps) {
function BestArticlesSection() {
const [bestArticles, setBestArticles] = useState<ArticleListProps[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [limit, setLimit] = useState<number>(getBestArticlesLimit());

const fetchBestArticles = async () => {
if (typeof window !== "undefined") {
const bestArticlesLimit = getBestArticlesLimit();
const bestArticles = await getBestArticles({
limit: bestArticlesLimit,
order: "like",
});
setBestArticles(bestArticles);
setLoading(false);
}
};

useEffect(() => {
fetchBestArticles();
}, [limit]);

const handleResize = () => {
setLimit(getBestArticlesLimit());
fetchBestArticles();
};

useResizeHandler(handleResize, 500);

if (loading) {
return <p>Loading...</p>;
}

return (
<ul className={styles.bestArticles}>
{articles.map((article) => (
<li key={article.id}>
<BestArticlesCard {...article} />
</li>
))}
</ul>
<div>
{loading ? (
<p>Loading...</p>
) : (
<ul className={styles.bestArticles}>
{bestArticles.map((article) => (
<li key={article.id}>
<BestArticlesCard {...article} />
</li>
))}
</ul>
)}
</div>
);
}

Expand Down
109 changes: 109 additions & 0 deletions components/CommentSection.tsx
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;
8 changes: 2 additions & 6 deletions components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import styles from "./Dropdown.module.css";
interface DropdownContainerProps {
onSortByNewest: () => void;
onSortByBest: () => void;
order: string;
order: "recent" | "like";
}

const Dropdown = ({
Expand All @@ -14,12 +14,8 @@ const Dropdown = ({
order,
}: DropdownContainerProps) => {
const [isDropdownView, setDropdownView] = useState(false);
const [buttonText, setButtonText] = useState("");

useEffect(() => {
const text = order === "recent" ? "최신순" : "인기순";
setButtonText(text);
}, [order]);
const buttonText = order === "recent" ? "최신순" : "인기순";

const handleClickContainer = () => {
setDropdownView((prevState) => !prevState);
Expand Down
Loading

0 comments on commit 853d6e8

Please sign in to comment.