Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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: 2 additions & 0 deletions src/api/feeds/getFeedDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface FeedDetailData {
aliasColor: string;
postDate: string;
isbn: string;
bookImageUrl: string;
bookTitle: string;
bookAuthor: string;
contentBody: string;
Expand All @@ -20,6 +21,7 @@ export interface FeedDetailData {
isLiked: boolean;
isPublic: boolean;
tagList: string[];
isWriter: boolean;
}

// API 응답 타입
Expand Down
33 changes: 33 additions & 0 deletions src/api/rooms/getDailyGreeting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { apiClient } from '../index';
import { type TodayCommentItem } from '../../types/today';

// 오늘의 한마디 조회 응답 타입
export interface DailyGreetingResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
todayCommentList: TodayCommentItem[];
nextCursor: string;
isLast: boolean;
};
}

// 오늘의 한마디 조회 요청 파라미터 타입
export interface DailyGreetingParams {
roomId: number;
cursor?: string;
}

export const getDailyGreeting = async ({ roomId, cursor }: DailyGreetingParams): Promise<DailyGreetingResponse> => {
try {
const params = cursor ? { cursor } : {};
const response = await apiClient.get<DailyGreetingResponse>(`/rooms/${roomId}/daily-greeting`, {
params
});
return response.data;
} catch (error) {
console.error('오늘의 한마디 조회 API 오류:', error);
throw error;
}
};
101 changes: 83 additions & 18 deletions src/components/common/Modal/MoreMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,49 @@ import styled from '@emotion/styled';
import { colors, typography } from '@/styles/global/global';
import type { MoreMenuProps } from '@/stores/usePopupStore';

const MoreMenu = ({ onEdit, onDelete, onClose }: MoreMenuProps) => {
const MoreMenu = ({ onEdit, onDelete, onClose, onReport, isWriter, type }: MoreMenuProps) => {
return (
<Overlay onClick={onClose}>
<Container onClick={e => e.stopPropagation()}>
<Button variant="edit" onClick={onEdit}>
수정하기
</Button>
<Button variant="delete" onClick={onDelete}>
삭제하기
</Button>
</Container>
{type === 'post' ? (
// post 타입: 기존 로직 유지
<>
{isWriter ? (
<>
<Container onClick={e => e.stopPropagation()}>
<Button variant="edit" onClick={onEdit}>
수정하기
</Button>
<Button variant="delete" onClick={onDelete}>
삭제하기
</Button>
</Container>
</>
) : (
<ReportContainer onClick={e => e.stopPropagation()}>
<Button variant="report" onClick={onReport}>
신고하기
</Button>
</ReportContainer>
)}
</>
) : (
// reply 타입: isWriter에 따라 삭제하기 또는 신고하기만 표시
<>
{isWriter ? (
<ReportContainer onClick={e => e.stopPropagation()}>
<Button variant="delete" onClick={onDelete}>
삭제하기
</Button>
</ReportContainer>
) : (
<ReportContainer onClick={e => e.stopPropagation()}>
<Button variant="report" onClick={onReport}>
신고하기
</Button>
</ReportContainer>
)}
</>
)}
</Overlay>
);
};
Expand Down Expand Up @@ -52,25 +84,58 @@ const Container = styled.div`
z-index: 1201;
`;

const Button = styled.div<{ variant: 'edit' | 'delete' }>`
const ReportContainer = styled.div`
position: fixed;
left: 0;
right: 0;
bottom: 0;

display: flex;
flex-direction: column;
min-width: 320px;
max-width: 767px;
width: 100%;
height: 90px;
padding: 20px;
border-radius: 12px 12px 0px 0px;
background-color: ${colors.darkgrey.main};
z-index: 1201;
`;

const Button = styled.div<{ variant: 'edit' | 'delete' | 'report' }>`
display: flex;
height: 50px;
align-items: center;
color: ${({ variant }) => (variant === 'edit' ? colors.white : colors.red)};
color: ${({ variant }) => {
if (variant === 'edit') return colors.white;
if (variant === 'delete') return colors.red;
if (variant === 'report') return colors.red;
return colors.white;
}};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.semibold};
line-height: 24px;
border-bottom: 1px solid ${colors.grey[400]};
cursor: pointer;

&:first-of-type {
padding: 8px 12px 16px 12px;
}
${({ variant }) => {
if (variant === 'report') {
return `
padding: 0;
border-bottom: none;
`;
}
return `
&:first-of-type {
padding: 8px 12px 16px 12px;
}

&:last-of-type {
padding: 16px 12px 8px 12px;
border-bottom: none;
}
&:last-of-type {
padding: 16px 12px 8px 12px;
border-bottom: none;
}
`;
}}
`;

export default MoreMenu;
36 changes: 25 additions & 11 deletions src/components/common/Post/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const Reply = ({
const containerRef = useRef<HTMLDivElement>(null);

const { startReply } = useReplyActions();
const { openMoreMenu, closePopup, openConfirm, openSnackbar } = usePopupActions();
const { openMoreMenu, closePopup, openSnackbar } = usePopupActions();

const handleLike = async () => {
try {
Expand Down Expand Up @@ -93,16 +93,30 @@ const Reply = ({
};

const handleMoreClick = () => {
openMoreMenu({
onDelete: () => {
openConfirm({
title: '이 댓글을 삭제하시겠어요?',
disc: '삭제 후에는 되돌릴 수 없어요',
onConfirm: handleDelete,
});
},
onClose: closePopup,
});
if (isWriter) {
// 작성자인 경우: 삭제하기만 표시
openMoreMenu({
onDelete: handleDelete,
type: 'reply',
isWriter: true,
onClose: closePopup,
});
} else {
// 작성자가 아닌 경우: 신고하기만 표시
openMoreMenu({
onReport: () => {
closePopup();
openSnackbar({
message: '신고가 접수되었어요.',
variant: 'top',
onClose: closePopup,
});
},
type: 'reply',
isWriter: false,
onClose: closePopup,
});
}
};

// 삭제된 댓글인 경우 처리
Expand Down
38 changes: 26 additions & 12 deletions src/components/common/Post/SubReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const SubReply = ({
const containerRef = useRef<HTMLDivElement>(null);

const { startReply } = useReplyActions();
const { openMoreMenu, closePopup, openConfirm, openSnackbar } = usePopupActions();
const { openMoreMenu, closePopup, openSnackbar } = usePopupActions();

const handleReplyClick = () => {
startReply(creatorNickname, commentId);
Expand All @@ -56,6 +56,7 @@ const SubReply = ({
}
};

// 이전 더보기 모달
// const handleMoreClick = () => {
// if (containerRef.current) {
// const rect = containerRef.current.getBoundingClientRect();
Expand Down Expand Up @@ -110,17 +111,30 @@ const SubReply = ({
};

const handleMoreClick = () => {
openMoreMenu({
onDelete: () => {
openConfirm({
title: '이 댓글을 삭제하시겠어요?',
disc: '삭제 후에는 되돌릴 수 없어요',
onConfirm: handleDelete,
onClose: closePopup,
});
},
onClose: closePopup,
});
if (isWriter) {
// 작성자인 경우: 삭제하기만 표시
openMoreMenu({
onDelete: handleDelete,
type: 'reply',
isWriter: true,
onClose: closePopup,
});
} else {
// 작성자가 아닌 경우: 신고하기만 표시
openMoreMenu({
onReport: () => {
closePopup();
openSnackbar({
message: '신고가 접수되었어요.',
variant: 'top',
onClose: closePopup,
});
},
type: 'reply',
isWriter: false,
onClose: closePopup,
});
}
};

// 삭제된 댓글인 경우 처리
Expand Down
6 changes: 5 additions & 1 deletion src/components/today-words/MessageList/MessageList.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ export const UserInfo = styled.div`
gap: 4px;
`;

export const UserAvatar = styled.div`
export const UserAvatar = styled.div<{ profileImageUrl?: string }>`
width: 36px;
height: 36px;
border-radius: 50%;
background-color: ${semanticColors.background.card};
border: 1px solid #3d3d3d;
flex-shrink: 0;
background-image: ${props => props.profileImageUrl ? `url(${props.profileImageUrl})` : 'none'};
background-size: cover;
background-position: center;
background-repeat: no-repeat;
`;

export const UserDetails = styled.div`
Expand Down
28 changes: 9 additions & 19 deletions src/components/today-words/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
{
messages: initialMessages,
currentUserId = 'user.01',
onMessageDelete,
isRealTimeMode = false,
},
ref,
) => {
Expand Down Expand Up @@ -72,10 +70,9 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
addMessage,
}));

// 먼저 모든 메시지를 시간순으로 정렬
const sortedMessages = messages.sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
);
// 먼저 모든 메시지를 시간순으로 정렬 (아래로 올수록 최신)
// ID를 기준으로 정렬 (ID가 클수록 최신)
const sortedMessages = messages.sort((a, b) => parseInt(a.id) - parseInt(b.id));

// 날짜별로 메시지 그룹화
const groupedMessages = sortedMessages.reduce(
Expand All @@ -90,7 +87,7 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
{} as Record<string, Message[]>,
);

// 날짜를 최신순으로 정렬
// 날짜를 오래된 순으로 정렬 (아래로 올수록 최신)
const sortedDates = Object.keys(groupedMessages).sort((a, b) => a.localeCompare(b));

const handleMoreClick = (messageId: string) => {
Expand All @@ -103,16 +100,9 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(

const handleDelete = () => {
if (selectedMessageId) {
if (isRealTimeMode && onMessageDelete) {
// 실시간 모드일 때는 부모 컴포넌트의 상태를 업데이트
onMessageDelete(selectedMessageId);
} else {
// 더미 모드일 때는 내부 상태만 업데이트
setMessages(prevMessages =>
prevMessages.filter(message => message.id !== selectedMessageId),
);
}
console.log(`메시지 ID ${selectedMessageId} 삭제됨`);
// TODO: 실제 삭제 API 연동 필요
console.log(`메시지 ID ${selectedMessageId} 삭제 요청 (API 개발 대기중)`);
alert('삭제 기능은 현재 개발 중입니다.');
}
setSelectedMessageId(null);
};
Expand All @@ -123,7 +113,7 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
};

const selectedMessage = messages.find(msg => msg.id === selectedMessageId);
const isMyMessage = selectedMessage?.user === currentUserId;
const isMyMessage = selectedMessage?.isWriter === true;

return (
<>
Expand All @@ -138,7 +128,7 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
{groupedMessages[date].map(message => (
<MessageItem key={message.id}>
<UserInfo>
<UserAvatar />
<UserAvatar profileImageUrl={message.profileImageUrl} />
<UserDetails>
<UserName>{message.user}</UserName>
<TimeStamp>{message.timeAgo}</TimeStamp>
Expand Down
Loading