Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[48기 이인재] PostList 기능 구현 #3

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
27f252f
Add : Thread PostList 및 Feed UI 구현
llinjae Aug 11, 2023
19e0492
Docs : README.md에 convention 내용 추가
llinjae Aug 11, 2023
b544d92
Docs : README.md에 convention 내용 추가
llinjae Aug 11, 2023
4151d23
Modify : PostList 레이아웃 변경
llinjae Aug 12, 2023
0654d26
Add : Mock Data로 동적인 관리
llinjae Aug 12, 2023
f3c2d81
Refactor : common.scss conflict 해결
llinjae Aug 12, 2023
b967fc5
ADD : 게시글 삭제 기능
llinjae Aug 13, 2023
c1f80e3
Refactor : 불필요 코드 제거
llinjae Aug 13, 2023
4e95ad9
Modify : Mock Data 수정 및 적용
llinjae Aug 13, 2023
df2861a
Add : Feed의 프로필 영역 클릭시 내용 숨기기 구현
llinjae Aug 13, 2023
b8f7a4b
Modify : FeedContent 숨긴 상태가 초기 상태가 되도록 변경
llinjae Aug 13, 2023
1a0dd15
Refactor : feed Content 영역 컴포넌트화
llinjae Aug 14, 2023
b927f52
Modify : Feed Profile 영역 스타일 변경
llinjae Aug 14, 2023
20e1abe
Add : Feed Detail UI 구현
llinjae Aug 14, 2023
d2197b9
Add : Feed 클릭시 댓글창(detail) 나오도록 구현
llinjae Aug 14, 2023
9598a51
Modify : 댓글 UI style 변경
llinjae Aug 14, 2023
08d2429
Refactor : 삭제 버튼 및 하트 버튼 클릭시 댓글 영역 토글되는 것 방지
llinjae Aug 15, 2023
36dd1a9
Refactor : Mentor 리뷰 반영
llinjae Aug 15, 2023
c726390
ADD : 댓글 게시 기능 구현 완료
llinjae Aug 15, 2023
158a86a
ADD : 댓글 삭제 기능 구현 완료
llinjae Aug 16, 2023
7f79db3
ADD : localStorage로 좋아요 state 저장
llinjae Aug 16, 2023
9b1189d
Refactor : Button, Input 컴포넌트 재사용
llinjae Aug 16, 2023
223d6f2
Refactor : 좋아요 수가 0밑으로 내려가는 것 방지
llinjae Aug 16, 2023
da2c16d
Add : 글쓰기 버튼 클릭시 게시글 작성 페이지로 이동
llinjae Aug 16, 2023
f54f2a0
Refactor : FormatDate utils 폴더에 따로 보관하여 사용
llinjae Aug 16, 2023
45b0a62
Modify : Mock Data 수정
llinjae Aug 16, 2023
25bf773
Modify : fetch할 때 렌더링 안 되는 것 해결
llinjae Aug 17, 2023
e9f57bd
Refactor : 삭제 버튼 및 하트 아이콘 클릭시 이벤트 전파 방지
llinjae Aug 17, 2023
389d715
Add : currentUser가 아니면 삭제 수정 버튼 사라지게 구현
llinjae Aug 17, 2023
0ff885c
Refactor : className warning 해결
llinjae Aug 17, 2023
42f1cb6
Modify : feed css 변경
llinjae Aug 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 PR에선 README.md 파일이 삭제되면 안됩니다.
다른 PR에서 README.md 파일이 수정된 후 이 PR이 병합되면, 수정한 내용까지 지워버릴 수도 있습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로 생성해서 깃헙에 있는 내용들 추가했는데 맞을까요..?

This file was deleted.

34 changes: 34 additions & 0 deletions public/data/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"id": 0,
"userImage": "https://ca.slack-edge.com/TH0U6FBTN-U05CNSZS4ET-02b82fbf4c55-512",
"nickname": "이인재",
"content": "일라이자 효과는 인간의 사고 과정과 감정을 AI 시스템에 잘못 돌리는 사람들의 경향을 말하며, 따라서 시스템이 실제보다 더 지능적이라고 믿는다. 이 현상은 1966년 MIT 교수 조셉 웨이젠바움이 만든 챗봇인 ELIZA의 이름을 따서 명명되었다.",
"createdAt": 1689087600000,
"updatedAt": 11
},
{
"id": 1,
"userImage": "https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjF8fHBlcnNvbnxlbnwwfHwwfHx8MA%3D%3D&auto=format&fit=crop&w=500&q=60",
"nickname": "John",
"content": "일라이자 효과는 컴퓨터 과학에서, 무의식적으로 컴퓨터의 행위를 인간의 행위와 유사한 것으로 추정하고 의인화하는 경향이다.",
"createdAt": 1686495600000,
"updatedAt": 11
},
{
"id": 2,
"userImage": "https://images.unsplash.com/photo-1552058544-f2b08422138a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Nnx8cGVyc29ufGVufDB8fDB8fHww&auto=format&fit=crop&w=500&q=60",
"nickname": "Julia",
"content": "성별과 성격과 같은 인간의 특성을 AI 음성 비서에게 돌리기",
"createdAt": 1691830007441,
"updatedAt": 11
},
{
"id": 3,
"userImage": "https://images.unsplash.com/photo-1500048993953-d23a436266cf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTV8fHBlcnNvbnxlbnwwfHwwfHx8MA%3D%3D&auto=format&fit=crop&w=500&q=60",
"nickname": "pliossun",
"content": "성별과 성격과 같은 인간의 특성을 AI 음성 비서에게 돌리기",
"createdAt": 1676127600000,
"updatedAt": 11
}
]
Empty file removed public/images/d.svg
Empty file.
3 changes: 3 additions & 0 deletions public/images/fillheart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 49 additions & 2 deletions src/pages/PostList/PostList.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,54 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import './PostList.scss';
import Feed from './components/Feed';
llinjae marked this conversation as resolved.
Show resolved Hide resolved

const PostList = () => {
return <div>PostList</div>;
const [feedsData, setFeedsData] = useState([]);

useEffect(() => {
fetch('/data/data.json', {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
})
.then(res => res.json())
.then(data => setFeedsData(data));
}, []);

const noListMessage = '게시글이 없습니다.';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트가 렌더링 될 때마다 다시 그려질 필요가 없는 상수 데이터이기 때문에, 컴포넌트 외부에 작성해도 됩니다.

그리고 개인의 성향일 수 있지만, '게시글이 없다'라는 text는 따로 관리할 중요한 변수는 아닌 것 같다는 생각이 드네요! 인재님만의 기준을 세워가는 것이 중요할 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러면 게시글이 없는 것을 현재 상태에서 어떻게 보여주면 좋을까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀드린 제 기준에 따른다면, 따로 변수로 지정하지 않고 리터럴하게 직접 JSX Element 안에 작성할 것 같습니다.


const handleRemove = targetId => {
const newList = feedsData.filter(data => data.id !== targetId);
setFeedsData(newList);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 기능을 실제로 백엔드 서버와 붙이는 것까지 고려했다면, 이 부분은 프론트 단에서만 작동하는 코드이기 때문에 실제로 반영하려면 다른 방식이 필요할 수도 있습니다.
추후에 서버와 기능을 붙여볼 때 참고해 주세요!


return (
<div className="postListContainer">
<div className="postListContentContainer">
<div className="feedContainer">
<div className="feedContentContainer">
llinjae marked this conversation as resolved.
Show resolved Hide resolved
{feedsData.length === 0 ? (
<p>{noListMessage}</p>
) : (
<>
{feedsData.map((feedData, index) => (
<Feed
key={index}
feedData={feedData}
handleRemove={handleRemove}
/>
))}
</>
)}
llinjae marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
<div className="btnBox">
<button className="btn">글 쓰기</button>
</div>
</div>
</div>
);
};

export default PostList;
57 changes: 57 additions & 0 deletions src/pages/PostList/PostList.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@import '../../style/variables.scss';

.postListContainer {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 80vh;
}

.postListContentContainer {
max-width: 100%;
max-height: 100%;
width: 100%;
height: calc(100% - 80px);
llinjae marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}

.feedContainer {
width: 90%;
height: 85%;

display: flex;
flex-direction: column;

.feedContentContainer {
overflow-y: scroll;

&::-webkit-scrollbar {
display: none;
}
}
}

.btnBox {
width: 90%;
height: 56px;
display: flex;
justify-content: end;

.btn {
border: none;
border-radius: 6px;
background-color: $blue;
color: white;
font-weight: 800;
font-size: 16px;
width: 120px;
llinjae marked this conversation as resolved.
Show resolved Hide resolved
padding: 0px 34px;
justify-content: center;
align-items: center;
cursor: pointer;
}
}
44 changes: 44 additions & 0 deletions src/pages/PostList/components/CommentItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import './CommentItem.scss';

const CommentItem = ({ feedData, feedDate }) => {
return (
<>
<form className="inputForm">
<input
type="text"
placeholder="댓글을 작성해주세요."
className="feedInput"
/>
<button className="postBtn">댓글 게시</button>
</form>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inputForm, postBtn 등은 다른 컴포넌트에서도 쓰일 수 있는 className인 만큼, 최상단을 Fragment가 아닌 element로 작성하고 className을 부여해 nesting해주시는 게 안전하겠네요!


<div className="commentContainer">
<div className="commentProfileBox">
<img
className="commentProfileImg"
src={feedData.userImage}
alt="프로필 이미지"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt 값에는 '이미지', 'image'등의 텍스트는 작성하시지 않아도 됩니다.

/>
<div className="commentRightBox">
<div className="commentDetailUserInfo">
<p className="userName">
<b>{feedData.nickname}</b>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feedData도 구조분해할당해서 사용하면 더 편리하겠네요!

</p>
<div className="commentProfileBoxRight">
<p className="date">{feedDate}</p>
<button className="deleteBtn">삭제</button>
<button className="editBtn">수정</button>
</div>
</div>
<p className="userCommentBox">
sdafasddsafasdfasdfsafasfasdfjssdafasddsafasdfasdfsafasfasdfjssdafasddsafasdfasdfsafasfasdfjssdafasddsafasdfasdfsafasfasdfjssdafasddsafasdfasdfsafasfasdfjssdafasddsafasdfasdfsafasfasdfjs
</p>
</div>
</div>
</div>
</>
);
};

export default CommentItem;
89 changes: 89 additions & 0 deletions src/pages/PostList/components/CommentItem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@import '../../../style/variables.scss';
.inputForm {
width: 100%;
display: grid;
grid-template-columns: 6fr 1fr;
gap: 8px;
height: 50px;
margin: 24px 0;

.feedInput {
border: 1px solid #e6e6e6;
border-radius: 4px;
}

.postBtn {
border: 1px solid $grey60;
background-color: #fff;
border-radius: 4px;
cursor: pointer;
}
}

.commentContainer {
max-width: 100%;
width: 100%;
height: auto;

.commentProfileBox {
display: flex;
justify-content: space-between;
gap: 24px;
padding: 0 8px;
margin-bottom: 24px;
height: auto;

.commentProfileImg {
width: 32px;
height: 32px;
border-radius: 50%;
}

.commentRightBox {
width: 100%;
display: flex;
flex-direction: column;

.commentDetailUserInfo {
display: flex;
justify-content: space-between;

.userName {
font-size: 16px;
}
}

.userCommentBox {
width: 100%;
word-break: break-all;
}
}

.commentProfileBoxRight {
display: flex;
align-items: center;

.date {
color: $grey60;
font-weight: 400;
}

.deleteBtn {
outline: none;
border: none;
background-color: transparent;
color: $red;
font-size: 14px;
cursor: pointer;
}

.editBtn {
outline: none;
border: none;
background-color: transparent;
font-size: 14px;
cursor: pointer;
}
}
}
}
72 changes: 72 additions & 0 deletions src/pages/PostList/components/Feed.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useState } from 'react';
import './Feed.scss';
import CommentItem from './CommentItem';

const Feed = ({ feedData, handleRemove }) => {
const [heartToggle, setHeartToggle] = useState(false);
const [like, setLike] = useState(0);
const [hideFeedContent, setHideFeedContent] = useState(true);
llinjae marked this conversation as resolved.
Show resolved Hide resolved

const feedDate = new Date(feedData.createdAt).toLocaleDateString();

const handleHeartToggle = () => {
setHeartToggle(!heartToggle);
setLike(heartToggle ? like - 1 : like + 1);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도, 위에 삭제 관련된 코멘트 참고해주세요!


const handleHideFeedContent = () => {
setHideFeedContent(!hideFeedContent);
};

return (
<>
<div className="feed" onClick={handleHideFeedContent}>
<div className="feedProfileBox">
<div className="feedProfileBoxLeft">
<img
className="profileImg"
src={feedData.userImage}
alt="프로필 이미지"
/>
<p className="userName">
<b>{feedData.nickname}</b>
</p>
</div>
<div className="feedProfileBoxRight">
<p className="date">{feedDate}</p>
<button
className="deleteBtn"
onClick={() => handleRemove(feedData.id)}
>
삭제
</button>
</div>
</div>
<div className="feedContentBox">
<p className="feedContent">{feedData.content}</p>
</div>
<div className="feedDescriptionBox">
<div className="feedDescriptionTop">
<p>좋아요 {like}</p>
<p>댓글 00</p>
</div>
<img
onClick={handleHeartToggle}
className="feedHeartImg"
src={
heartToggle
? process.env.PUBLIC_URL + '/images/fillheart.svg'
: process.env.PUBLIC_URL + '/images/heart.svg'
}
alt="좋아요 버튼"
llinjae marked this conversation as resolved.
Show resolved Hide resolved
/>
</div>
</div>
{!hideFeedContent && (
<CommentItem feedData={feedData} feedDate={feedDate} />
)}
</>
);
};

export default Feed;
Loading