Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/assets/feed/activeLike.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/assets/feed/like.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/feed/replyIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import styled from '@emotion/styled';
import { useNavigate } from 'react-router-dom';
import BookInfoCard from './BookInfoCard';
import BookInfoCard from '../../feed/BookInfoCard';
import type { PostBodyProps } from '@/types/post';

const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 16px;
cursor: pointer;
`;

const PostContent = styled.div<{ hasImage: boolean }>`
Expand Down Expand Up @@ -76,17 +76,9 @@ const TagContainer = styled.div`
}
`;

interface PostBodyProps {
bookTitle: string;
bookAuthor: string;
postContent: string;
postId: string;
images?: string[];
tags?: string[];
}

const PostBody = ({
bookTitle,
isbn,
bookAuthor,
postContent,
postId,
Expand All @@ -98,13 +90,14 @@ const PostBody = ({
const hasTag = tags.length > 0;

const handlePostContent = () => {
// if (!isClickable) return;
navigate(`/feed/${postId}`);
// API 연동시 경로 수정 필요
};

return (
<Container>
<BookInfoCard bookTitle={bookTitle} bookAuthor={bookAuthor} />
<BookInfoCard bookTitle={bookTitle} bookAuthor={bookAuthor} isbn={isbn} />
<PostContent hasImage={hasImage} onClick={handlePostContent}>
<div className="content">{postContent}</div>
{hasImage && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import like from '../../assets/feed/like.svg';
import activeLike from '../../assets/feed/activeLike.svg';
import comment from '../../assets/feed/comment.svg';
import save from '../../assets/feed/save.svg';
import activeSave from '../../assets/feed/activeSave.svg';
import lockIcon from '../../assets/feed/lockIcon.svg';
import like from '../../../assets/feed/like.svg';
import activeLike from '../../../assets/feed/activeLike.svg';
import comment from '../../../assets/feed/comment.svg';
import save from '../../../assets/feed/save.svg';
import activeSave from '../../../assets/feed/activeSave.svg';
import lockIcon from '../../../assets/feed/lockIcon.svg';

const Container = styled.div`
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';

const Container = styled.div`
interface PostHeaderProps {
profileImgUrl: string;
userName: string;
userTitle: string;
titleColor: string;
createdAt: string;
userId: number;
type?: 'post' | 'reply';
}

const PostHeader = ({
profileImgUrl,
userName,
userTitle,
titleColor,
createdAt,
userId,
type = 'post',
}: PostHeaderProps) => {
const navigate = useNavigate();

const handleClick = () => {
navigate(`/otherfeed/${userId}`);
};
return (
<Container type={type} onClick={handleClick}>
<div className="headerInfo">
<img src={profileImgUrl} alt="칭호 이미지" />
<div className="infoBox">
<div className="username">{userName}</div>
<div className="usertitle" style={{ color: titleColor }}>
{userTitle}
</div>
</div>
</div>
<div className="timestamp">{createdAt}</div>
</Container>
);
};

const Container = styled.div<{ type?: 'post' | 'reply' }>`
width: 100%;
height: 36px;
height: ${({ type }) => (type === 'reply' ? '29px' : '36px')};
display: flex;
flex-direction: row;
align-items: center;
Expand All @@ -12,12 +53,12 @@ const Container = styled.div`
.headerInfo {
display: flex;
flex-direction: row;
gap: 8px;

align-items: center;
gap: ${({ type }) => (type === 'reply' ? '4px' : '8px')};
img {
width: 36px;
height: 36px;
border-radius: 36px;
width: ${({ type }) => (type === 'reply' ? '24px' : '36px')};
height: ${({ type }) => (type === 'reply' ? '24px' : '36px')};
border-radius: ${({ type }) => (type === 'reply' ? '24px' : '36px')};
border: 0.5px solid var(--color-grey-300);
}

Expand All @@ -28,13 +69,13 @@ const Container = styled.div`
gap: 4px;
.username {
color: var(--color-white);
font-size: var(--font-size-sm);
font-weight: 500;
font-size: ${({ type }) => (type === 'reply' ? '12px' : 'var(--font-size-sm)')};
font-weight: var(--font-weight-medium);
line-height: normal;
}
.usertitle {
color: var(--color-character-pink);
font-size: var(--font-size-xs);
font-size: ${({ type }) => (type === 'reply' ? '11px' : 'var(--font-size-xs)')};
font-weight: var(--font-weight-regular);
line-height: normal;
}
Expand All @@ -49,35 +90,4 @@ const Container = styled.div`
}
`;

interface PostHeaderProps {
profileImgUrl: string;
userName: string;
userTitle: string;
titleColor: string;
createdAt: string;
}

const PostHeader = ({
profileImgUrl,
userName,
userTitle,
titleColor,
createdAt,
}: PostHeaderProps) => {
return (
<Container>
<div className="headerInfo">
<img src={profileImgUrl} alt="칭호 이미지" />
<div className="infoBox">
<div className="username">{userName}</div>
<div className="usertitle" style={{ color: titleColor }}>
{userTitle}
</div>
</div>
</div>
<div className="timestamp">{createdAt}</div>
</Container>
);
};

export default PostHeader;
99 changes: 99 additions & 0 deletions src/components/common/Post/Reply.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from 'react';
import styled from '@emotion/styled';
import { typography, colors } from '@/styles/global/global';
import PostHeader from './PostHeader';
import type { ReplyDataProps } from '@/types/post';
import like from '../../../assets/feed/like.svg';
import activeLike from '../../../assets/feed/activeLike.svg';

const Reply = ({
profileImgUrl,
userName,
userId,
userTitle,
titleColor,
createdAt,
initialLikeCount,
replyContent,
}: ReplyDataProps) => {
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);
const handleLike = () => {
setLiked(!liked);
setLikeCount(prev => (liked ? prev - 1 : prev + 1));
};

return (
<Container>
<PostHeader
profileImgUrl={profileImgUrl}
userName={userName}
userTitle={userTitle}
titleColor={titleColor}
createdAt={createdAt}
userId={userId}
type="reply"
/>
<ReplySection>
<div className="left">
<div className="reply">{replyContent}</div>
<div className="sub-reply">답글작성</div>
</div>
<div className="right">
<img src={liked ? activeLike : like} onClick={handleLike} />
<div className="count">{likeCount}</div>
</div>
</ReplySection>
</Container>
);
};

const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 12px;
`;

const ReplySection = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 20px;

.left {
display: flex;
flex-direction: column;
gap: 12px;

.reply {
color: ${colors.grey[100]};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: 20px;
}
.sub-reply {
color: ${colors.grey[300]};
font-size: ${typography.fontSize.xs};
font-weight: ${typography.fontWeight.semibold};
line-height: normal;
cursor: pointer;
}
}

.right {
display: flex;
flex-direction: column;
cursor: pointer;

.count {
text-align: center;
color: ${colors.grey[100]};
font-size: 10px;
font-weight: ${typography.fontWeight.medium};
line-height: normal;
}
}
`;
Comment on lines +51 to +97
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스타일 코드가 SubReply와 거의 동일합니다.

두 컴포넌트의 스타일링이 중복되어 유지보수성이 떨어집니다.

공통 스타일을 별도 파일로 분리하거나, 더 나은 방법으로는 하나의 기본 컴포넌트를 만들고 props로 차이점을 처리하는 것을 권장합니다:

// BaseReply.tsx
interface BaseReplyProps {
  showReplyIcon?: boolean;
  // ... other props
}

const BaseReply = ({ showReplyIcon = false, ...props }: BaseReplyProps) => {
  // 공통 로직
  return (
    <Container>
      {showReplyIcon && <ReplyIcon>...</ReplyIcon>}
      {/* 공통 렌더링 로직 */}
    </Container>
  );
};
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx between lines 48 and 94, the styling
code for Reply and SubReply components is almost identical, causing duplication
and reducing maintainability. Refactor by extracting the shared styles into a
single base styled component or a separate file, then create one base Reply
component that accepts props to handle the differences in styling or behavior.
This approach centralizes common styles and logic, improving code reuse and ease
of updates.


export default Reply;
81 changes: 81 additions & 0 deletions src/components/common/Post/ReplyList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import styled from '@emotion/styled';
import { typography, colors } from '@/styles/global/global';
import Reply from './Reply';
import SubReply from './SubReply';
import type { ReplyListProps } from '@/types/post';

const ReplyList = ({ commentList }: ReplyListProps) => {
const hasComments = commentList.length > 0;

return (
<Container>
{hasComments ? (
commentList.map(comment => (
<div className="comment-group" key={comment.commentId}>
<Reply {...comment} />
{comment.replyCommentList.map(sub => (
<SubReply key={sub.replyCommentId} {...sub} />
))}
</div>
))
) : (
<EmptyState>
<div className="title">아직 댓글이 없어요</div>
<div className="sub-title">첫번째 댓글을 남겨보세요</div>
</EmptyState>
)}
</Container>
);
};

const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
min-width: 360px;
max-width: 540px;
padding: 40px 20px;
margin: 0 auto;
margin-bottom: 56px;
gap: 24px;
flex: 1;

.comment-group {
display: flex;
flex-direction: column;
gap: 24px;
}
`;

const EmptyState = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
min-width: 360px;
max-width: 540px;
padding: 40px 20px;
margin: 0 auto;
margin-bottom: 56px;
gap: 8px;
flex: 1;

.title {
color: ${colors.white};
text-align: center;
font-size: ${typography.fontSize.lg};
font-weight: ${typography.fontWeight.semibold};
line-height: 24px;
}

.sub-title {
color: ${colors.grey[100]};
text-align: center;
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: normal;
}
`;

export default ReplyList;
Loading