Skip to content

Commit

Permalink
Merge pull request #289 from ojm51/Next-오정민-sprint10
Browse files Browse the repository at this point in the history
[오정민] Sprint10
  • Loading branch information
wlgns2223 authored Aug 19, 2024
2 parents f392f04 + 7a69714 commit 178bfa9
Show file tree
Hide file tree
Showing 38 changed files with 2,089 additions and 199 deletions.
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}
Binary file added assets/images/ic_kebab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions components/AddComment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Dispatch, SetStateAction, useState } from "react";
import axios from "@/lib/axios";
import { IComment } from "@/types/comment";

import TextInput from "./Inputs/TextInput";
import AddButton from "./Buttons/AddButton";

const INPUT_CONTENT = [
{
name: "content",
label: "댓글달기",
placeholder: "댓글을 입력해주세요",
isTextArea: true,
},
];

interface AddCommentProps {
id: number;
setCommentList: Dispatch<SetStateAction<IComment[]>>;
}

function AddComment({ id, setCommentList }: AddCommentProps) {
const [inputValue, setInputValue] = useState({ content: "" });
const [isFormComplete, setIsFormComplete] = useState(false);

const handleValueChange = (name: string, value: string) => {
setInputValue((prevValues) => ({
...prevValues,
[name]: value,
}));
setIsFormComplete(value.trim() !== "");
};

async function postComment() {
const accessToken = "";

let newComment: IComment;
try {
const response = await axios.post(
`/articles/${id}/comments`,
inputValue,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);

newComment = response.data ?? [];
console.log("post succeed: ", newComment);
setCommentList((prevCommentList) => [newComment, ...prevCommentList]);
} catch (error) {
console.error("댓글 등록 중 오류가 발생했습니다: ", error);
} finally {
setInputValue({
content: "",
});
}
}

return (
<div>
{INPUT_CONTENT.map((content, index) => {
return (
<TextInput
key={index}
content={content}
onChange={handleValueChange}
/>
);
})}
<div className="text-right">
<AddButton
buttonText="등록"
isFormComplete={isFormComplete}
onClick={postComment}
/>
</div>
</div>
);
}

export default AddComment;

// const [inputValue, setInputValue] = useState({
// content: "",
// });
// const [isFormComplete, setIsFormComplete] = useState(false);

// const handleValueChange = (name: string, value: string) => {
// setInputValue((prevValues) => ({
// ...prevValues,
// [name]: value,
// }));
// setIsFormComplete(value.trim() !== "");
// };

// async function postComment() {
// const accessToken =
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjUsInNjb3BlIjoiYWNjZXNzIiwiaWF0IjoxNzIzNzczNzEzLCJleHAiOjE3MjM3NzU1MTMsImlzcyI6InNwLXBhbmRhLW1hcmtldCJ9.heZocGCQOejK4JPnWgWzJ438vW1sE2RAsj5d6ZHIhbc";

// let newComment: IComment;
// try {
// const data = {
// ...inputValue,
// };
// const response = await axios.post(`/articles/${id}/comments`, data, {
// headers: {
// Authorization: `Bearer ${accessToken}`,
// },
// });
// newComment = response.data ?? [];
// setCommentList((prevCommentList) => [newComment, ...prevCommentList]);
// } catch (error) {
// console.error("댓글 등록 중 오류가 발생했습니다: ", error);
// }
// }
4 changes: 3 additions & 1 deletion components/AllArticleList/AllArticle.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.article {
display: inline-block;
width: 100%;
margin-top: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: solid 0.0625rem var(--gray200);
Expand All @@ -20,7 +22,7 @@
}

.productImage {
border: 0 0 0 0.0625rem var(--gray100) inset;
box-shadow: 0 0 0 0.0625rem var(--gray100) inset;
border-radius: 0.5rem;
}

Expand Down
20 changes: 11 additions & 9 deletions components/AllArticleList/AllArticle.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { useState } from "react";
import Link from "next/link";
import { Article } from "@/types/article";
import formatDate from "@/lib/formatDate";
import styles from "./AllArticle.module.css";

import Image from "next/image";
import profileImage from "@/assets/images/img_profile.png";
import likeImageFull from "@/assets/images/ic_heart_full.png";
import likeImageEmpty from "@/assets/images/ic_heart_empty.png";
import likeIconFull from "@/assets/images/ic_heart_full.png";
import likeIconEmpty from "@/assets/images/ic_heart_empty.png";
import defaultProductImage from "@/assets/images/img_product_empty.png";

interface AllArticleProps {
article: Article;
}

function AllArticle({ article }: AllArticleProps) {
const { title, image, writer, likeCount, createdAt } = article;

const { id, title, image, writer, likeCount, createdAt } = article;
const [isLikeClicked, setIsLikeClicked] = useState<boolean>(false);
const productImage = image || defaultProductImage;

const handleLikeButtonClick = () => {
setIsLikeClicked(!isLikeClicked);
setIsLikeClicked((prevIsLikeClicked) => !prevIsLikeClicked);
};

return (
<article className={styles.article}>
<Link className={styles.article} href={`/board/${id}`}>
<div className={styles.articleHeader}>
<h4 className={styles.title}>{title}</h4>
<Image
className={styles.productImage}
src={image}
src={productImage}
alt="상품 이미지"
width={72}
height={72}
Expand All @@ -49,7 +51,7 @@ function AllArticle({ article }: AllArticleProps) {
<button onClick={handleLikeButtonClick}>
<Image
className={styles.likeIcon}
src={isLikeClicked ? likeImageFull : likeImageEmpty}
src={isLikeClicked ? likeIconFull : likeIconEmpty}
alt="좋아요 아이콘"
width={24}
height={24}
Expand All @@ -58,7 +60,7 @@ function AllArticle({ article }: AllArticleProps) {
<h5 className={styles.likeCount}>{likeCount}</h5>
</div>
</div>
</article>
</Link>
);
}

Expand Down
28 changes: 4 additions & 24 deletions components/AllArticleList/AllArticleList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { Article } from "@/types/article";
import styles from "./AllArticleList.module.css";

import Image from "next/image";
import replyEmptyImage from "@/assets/images/img_reply_empty.png";
import AllArticle from "./AllArticle";
import ReturnButton from "@/components/ReturnButton";

interface AllArticleListProps {
articles: Article[];
Expand All @@ -13,26 +8,11 @@ interface AllArticleListProps {
function AllArticleList({ articles }: AllArticleListProps) {
return (
<div>
{!!articles.length ? (
articles.map((article: Article) => (
<div key={article.id}>
<AllArticle article={article} />
</div>
))
) : (
<div className={styles.emptyMessageWrapper}>
<Image
src={replyEmptyImage}
alt="일치하는 게시글이 없어요"
width={200}
height={200}
/>
<div className={styles.emptyMessageText}>
검색어와 일치하는 게시글이 없어요
</div>
{articles.map((article: Article) => (
<div key={article.id}>
<AllArticle article={article} />
</div>
)}
<ReturnButton href="/boards" text="전체 게시글로 돌아가기" />
))}
</div>
);
}
Expand Down
3 changes: 2 additions & 1 deletion components/BestArticleList/BestArticle.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.articleWrapper {
display: inline-block;
width: 24rem;
height: auto;
padding: 0 1.5rem 1rem;
Expand All @@ -25,7 +26,7 @@
}

.productImage {
border: 0 0 0 0.0625rem var(--gray200) inset;
box-shadow: 0 0 0 0.0625rem var(--gray200) inset;
border-radius: 0.375rem;
}

Expand Down
7 changes: 4 additions & 3 deletions components/BestArticleList/BestArticle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import Link from "next/link";
import { Article } from "@/types/article";
import formatDate from "@/lib/formatDate";
import styles from "./BestArticle.module.css";
Expand All @@ -13,7 +14,7 @@ interface BestArticleProps {
}

function BestArticle({ article }: BestArticleProps) {
const { title, image, writer, likeCount, createdAt } = article;
const { id, title, image, writer, likeCount, createdAt } = article;

const [isLikeClicked, setIsLikeClicked] = useState<boolean>(false);

Expand All @@ -22,7 +23,7 @@ function BestArticle({ article }: BestArticleProps) {
};

return (
<article className={styles.articleWrapper}>
<Link className={styles.articleWrapper} href={`/board/${id}`}>
<Image
className={styles.bestBadgeImage}
src={bestBadge}
Expand Down Expand Up @@ -57,7 +58,7 @@ function BestArticle({ article }: BestArticleProps) {
</div>
<h5 className={styles.createdAt}>{formatDate(createdAt)}</h5>
</div>
</article>
</Link>
);
}

Expand Down
25 changes: 25 additions & 0 deletions components/Buttons/AddButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
interface AddButtonProps {
buttonText: string;
isFormComplete?: boolean;
onClick: () => void;
}

function AddButton({
buttonText,
isFormComplete = false,
onClick,
}: AddButtonProps) {
let buttonClassNames = `rounded-lg px-6 py-2 text-gray-100 ${isFormComplete ? "bg-brand-blue" : "bg-gray-400"}`;

return (
<button
className={buttonClassNames}
disabled={!isFormComplete}
onClick={onClick}
>
{buttonText}
</button>
);
}

export default AddButton;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.button {
padding: 0.7188rem 1.4375rem;
padding: 0.6875rem 1.4375rem;
border-radius: 8px;
background-color: var(--blue);
font-size: 1rem;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
50 changes: 50 additions & 0 deletions components/CommentList/Comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { IComment } from "@/types/comment";
import getTimeElapsed from "@/lib/getTimeElapsed";

import Image from "next/image";
import defaultProfileImage from "@/assets/images/img_profile.png";
import kebabMenuIcon from "@/assets/images/ic_kebab.png";

interface CommentProps {
comment: IComment;
}

function Comment({ comment }: CommentProps) {
const { writer, content, updatedAt } = comment;
const profileImage = writer.image || defaultProfileImage;

const timeElapsed = getTimeElapsed(new Date(updatedAt));

return (
<div className="mb-6 border-b border-gray-200 bg-[#fcfcfc] pb-3">
<div className="mb-6 flex items-center justify-between">
<div>{content}</div>
<button>
<Image
src={kebabMenuIcon}
alt="케밥 메뉴 아아콘"
width={24}
height={24}
/>
</button>
</div>
<div className="flex items-center justify-start gap-2">
<Image
className="writer image"
src={profileImage}
alt={`${writer.nickname}`}
width={40}
height={40}
/>
<div className="flex flex-col items-start justify-start">
<div className="text-xs font-normal text-gray-600">
{writer.nickname}
</div>
<div className="text-xs font-normal text-gray-400">{timeElapsed}</div>
</div>
</div>
</div>
);
}

export default Comment;
Loading

0 comments on commit 178bfa9

Please sign in to comment.