diff --git a/src/api/feeds/getFeedDetail.ts b/src/api/feeds/getFeedDetail.ts index 75e5d338..f634e453 100644 --- a/src/api/feeds/getFeedDetail.ts +++ b/src/api/feeds/getFeedDetail.ts @@ -10,6 +10,7 @@ export interface FeedDetailData { aliasColor: string; postDate: string; isbn: string; + bookImageUrl: string; bookTitle: string; bookAuthor: string; contentBody: string; @@ -20,6 +21,7 @@ export interface FeedDetailData { isLiked: boolean; isPublic: boolean; tagList: string[]; + isWriter: boolean; } // API 응답 타입 diff --git a/src/api/rooms/getDailyGreeting.ts b/src/api/rooms/getDailyGreeting.ts new file mode 100644 index 00000000..8f236192 --- /dev/null +++ b/src/api/rooms/getDailyGreeting.ts @@ -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 => { + try { + const params = cursor ? { cursor } : {}; + const response = await apiClient.get(`/rooms/${roomId}/daily-greeting`, { + params + }); + return response.data; + } catch (error) { + console.error('오늘의 한마디 조회 API 오류:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/components/common/Modal/MoreMenu.tsx b/src/components/common/Modal/MoreMenu.tsx index 84165ffa..26fd6c72 100644 --- a/src/components/common/Modal/MoreMenu.tsx +++ b/src/components/common/Modal/MoreMenu.tsx @@ -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 ( - e.stopPropagation()}> - - - + {type === 'post' ? ( + // post 타입: 기존 로직 유지 + <> + {isWriter ? ( + <> + e.stopPropagation()}> + + + + + ) : ( + e.stopPropagation()}> + + + )} + + ) : ( + // reply 타입: isWriter에 따라 삭제하기 또는 신고하기만 표시 + <> + {isWriter ? ( + e.stopPropagation()}> + + + ) : ( + e.stopPropagation()}> + + + )} + + )} ); }; @@ -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; diff --git a/src/components/common/Post/Reply.tsx b/src/components/common/Post/Reply.tsx index 59f2b791..784285a3 100644 --- a/src/components/common/Post/Reply.tsx +++ b/src/components/common/Post/Reply.tsx @@ -35,7 +35,7 @@ const Reply = ({ const containerRef = useRef(null); const { startReply } = useReplyActions(); - const { openMoreMenu, closePopup, openConfirm, openSnackbar } = usePopupActions(); + const { openMoreMenu, closePopup, openSnackbar } = usePopupActions(); const handleLike = async () => { try { @@ -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, + }); + } }; // 삭제된 댓글인 경우 처리 diff --git a/src/components/common/Post/SubReply.tsx b/src/components/common/Post/SubReply.tsx index d83edffa..c3c5913f 100644 --- a/src/components/common/Post/SubReply.tsx +++ b/src/components/common/Post/SubReply.tsx @@ -37,7 +37,7 @@ const SubReply = ({ const containerRef = useRef(null); const { startReply } = useReplyActions(); - const { openMoreMenu, closePopup, openConfirm, openSnackbar } = usePopupActions(); + const { openMoreMenu, closePopup, openSnackbar } = usePopupActions(); const handleReplyClick = () => { startReply(creatorNickname, commentId); @@ -56,6 +56,7 @@ const SubReply = ({ } }; + // 이전 더보기 모달 // const handleMoreClick = () => { // if (containerRef.current) { // const rect = containerRef.current.getBoundingClientRect(); @@ -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, + }); + } }; // 삭제된 댓글인 경우 처리 diff --git a/src/components/today-words/MessageList/MessageList.styled.ts b/src/components/today-words/MessageList/MessageList.styled.ts index 256fdd34..7de63227 100644 --- a/src/components/today-words/MessageList/MessageList.styled.ts +++ b/src/components/today-words/MessageList/MessageList.styled.ts @@ -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` diff --git a/src/components/today-words/MessageList/MessageList.tsx b/src/components/today-words/MessageList/MessageList.tsx index ff9f73b0..10a8b703 100644 --- a/src/components/today-words/MessageList/MessageList.tsx +++ b/src/components/today-words/MessageList/MessageList.tsx @@ -34,8 +34,6 @@ const MessageList = forwardRef( { messages: initialMessages, currentUserId = 'user.01', - onMessageDelete, - isRealTimeMode = false, }, ref, ) => { @@ -72,10 +70,9 @@ const MessageList = forwardRef( 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( @@ -90,7 +87,7 @@ const MessageList = forwardRef( {} as Record, ); - // 날짜를 최신순으로 정렬 + // 날짜를 오래된 순으로 정렬 (아래로 올수록 최신) const sortedDates = Object.keys(groupedMessages).sort((a, b) => a.localeCompare(b)); const handleMoreClick = (messageId: string) => { @@ -103,16 +100,9 @@ const MessageList = forwardRef( 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); }; @@ -123,7 +113,7 @@ const MessageList = forwardRef( }; const selectedMessage = messages.find(msg => msg.id === selectedMessageId); - const isMyMessage = selectedMessage?.user === currentUserId; + const isMyMessage = selectedMessage?.isWriter === true; return ( <> @@ -138,7 +128,7 @@ const MessageList = forwardRef( {groupedMessages[date].map(message => ( - + {message.user} {message.timeAgo} diff --git a/src/pages/feed/FeedDetailPage.tsx b/src/pages/feed/FeedDetailPage.tsx index 048346ba..8e24eb51 100644 --- a/src/pages/feed/FeedDetailPage.tsx +++ b/src/pages/feed/FeedDetailPage.tsx @@ -86,51 +86,71 @@ const FeedDetailPage = () => { }; const handleMoreClick = () => { - openMoreMenu({ - onEdit: () => { - closePopup(); - navigate(`/post/update/${feedId}`); - }, - onClose: () => { - closePopup(); - }, - onDelete: () => { - openConfirm({ - title: '이 피드를 삭제하시겠어요?', - disc: '삭제 후에는 되돌릴 수 없어요', - onClose: closePopup, - onConfirm: async () => { - try { - if (!feedId) return; - const resp = await deleteFeedPost(Number(feedId)); - if (resp.isSuccess) { - closePopup(); + if (feedData?.isWriter) { + // 작성자인 경우: 수정하기, 삭제하기 메뉴 + openMoreMenu({ + onEdit: () => { + closePopup(); + // navigate(`/post/update/${feedId}`); + }, + onClose: () => { + closePopup(); + }, + onDelete: () => { + openConfirm({ + title: '이 피드를 삭제하시겠어요?', + disc: '삭제 후에는 되돌릴 수 없어요', + onClose: closePopup, + onConfirm: async () => { + try { + if (!feedId) return; + const resp = await deleteFeedPost(Number(feedId)); + if (resp.isSuccess) { + closePopup(); + openSnackbar({ + message: '피드 삭제를 완료했어요.', + variant: 'top', + onClose: closePopup, + }); + navigate('/feed', { state: { initialTab: '내 피드' } }); + } else { + openSnackbar({ + message: '피드 삭제를 실패했어요.', + variant: 'top', + onClose: closePopup, + }); + } + } catch (e) { + console.error('피드 삭제 실패:', e); openSnackbar({ - message: '피드 삭제를 완료했어요.', - variant: 'top', - onClose: closePopup, - }); - // 즉시 /feed로 리다이렉트 - navigate('/feed', { state: { initialTab: '내 피드' } }); - } else { - openSnackbar({ - message: '피드 삭제를 실패했어요.', + message: '피드 삭제 중 오류가 발생했어요.', variant: 'top', onClose: closePopup, }); } - } catch (e) { - console.error('피드 삭제 실패:', e); - openSnackbar({ - message: '피드 삭제 중 오류가 발생했어요.', - variant: 'top', - onClose: closePopup, - }); - } - }, - }); - }, - }); + }, + }); + }, + isWriter: true, + type: 'post', + }); + } else { + openMoreMenu({ + onClose: () => { + closePopup(); + }, + onReport: () => { + closePopup(); + openSnackbar({ + message: '신고가 접수되었어요.', + variant: 'top', + onClose: closePopup, + }); + }, + isWriter: false, + type: 'post', + }); + } }; const handleBackClick = () => { diff --git a/src/pages/today-words/TodayWords.tsx b/src/pages/today-words/TodayWords.tsx index 632cfc8a..f80932d7 100644 --- a/src/pages/today-words/TodayWords.tsx +++ b/src/pages/today-words/TodayWords.tsx @@ -1,15 +1,16 @@ -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import TitleHeader from '../../components/common/TitleHeader'; import EmptyState from '../../components/today-words/EmptyState'; import MessageList from '../../components/today-words/MessageList/MessageList'; import type { MessageListRef } from '../../components/today-words/MessageList/MessageList'; import MessageInput from '../../components/today-words/MessageInput'; +import LoadingSpinner from '../../components/common/LoadingSpinner'; import leftarrow from '../../assets/common/leftArrow.svg'; import { Container, ContentArea } from './TodayWords.styled'; -import type { Message } from '../../types/today'; -import { dummyMessages } from '../../constants/today-constants'; +import type { Message, TodayCommentItem } from '../../types/today'; import { createDailyGreeting } from '../../api/rooms/createDailyGreeting'; +import { getDailyGreeting } from '../../api/rooms/getDailyGreeting'; import { usePopupActions } from '../../hooks/usePopupActions'; const TodayWords = () => { @@ -19,15 +20,156 @@ const TodayWords = () => { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [nextCursor, setNextCursor] = useState(null); + const [isLast, setIsLast] = useState(false); + const [hasInitiallyLoaded, setHasInitiallyLoaded] = useState(false); const { openSnackbar } = usePopupActions(); - // 개발용: 빈 상태와 글 있는 상태 토글 - const [showMessages, setShowMessages] = useState(false); + // 하루 5개 제한 관련 + const DAILY_LIMIT = 5; + + // 오늘 작성한 내 메시지 개수 계산 + const getTodayMyMessageCount = useCallback(() => { + const today = new Date().toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).replace(/\. /g, '.').replace(/\.$/, ''); + + return messages.filter(message => + message.isWriter === true && message.timestamp === today + ).length; + }, [messages]); + + const todayMyMessageCount = getTodayMyMessageCount(); + const handleBackClick = () => { navigate(-1); }; + // API 데이터를 Message 타입으로 변환하는 함수 + const convertToMessage = (item: TodayCommentItem): Message => { + // 네트워크 응답에서 postDate가 "1일 전" 형태의 문자열로 오는 것으로 보임 + // 따라서 postDate를 그대로 timeAgo로 사용 + const timeAgo = item.postDate || '방금 전'; + + // createdAt은 현재 시간으로 설정 (정확한 시간이 필요하다면 다른 API 필드 사용) + const createdAt = new Date(); + + return { + id: item.attendanceCheckId.toString(), + user: item.creatorNickname, + content: item.todayComment, + timestamp: item.date, + timeAgo, + createdAt, + profileImageUrl: item.creatorProfileImageUrl, + isWriter: item.isWriter, + }; + }; + + // 오늘의 한마디 목록 조회 + const loadMessages = useCallback(async (cursor?: string, isRefresh = false) => { + if (!roomId) return; + + try { + if (isRefresh) { + setIsLoading(true); + } else { + setIsLoadingMore(true); + } + + const response = await getDailyGreeting({ + roomId: parseInt(roomId), + cursor: cursor || undefined, + }); + + if (response.isSuccess) { + const newMessages = response.data.todayCommentList.map(convertToMessage); + + if (isRefresh) { + setMessages(newMessages); + } else { + setMessages(prev => [...prev, ...newMessages]); + } + + setNextCursor(response.data.nextCursor); + setIsLast(response.data.isLast); + setHasInitiallyLoaded(true); + } else { + openSnackbar({ + message: response.message || '오늘의 한마디 목록을 불러오는데 실패했습니다.', + variant: 'top', + onClose: () => {}, + }); + } + } catch (error) { + console.error('오늘의 한마디 목록 조회 오류:', error); + + let errorMessage = '오늘의 한마디 목록을 불러오는 중 오류가 발생했습니다.'; + + if (error && typeof error === 'object' && 'response' in error) { + const axiosError = error as { + response?: { + data?: { + message?: string; + code?: number; + }; + }; + }; + + if (axiosError.response?.data?.message) { + errorMessage = axiosError.response.data.message; + } else if (axiosError.response?.data?.code === 403) { + errorMessage = '방 접근 권한이 없습니다.'; + } else if (axiosError.response?.data?.code === 404) { + errorMessage = '존재하지 않는 방입니다.'; + } + } + + openSnackbar({ + message: errorMessage, + variant: 'top', + onClose: () => {}, + }); + } finally { + setIsLoading(false); + setIsLoadingMore(false); + } + }, [roomId]); + + // 더 많은 메시지 로드 + const loadMoreMessages = useCallback(() => { + if (!isLoadingMore && !isLast && nextCursor && roomId) { + loadMessages(nextCursor); + } + }, [isLoadingMore, isLast, nextCursor, roomId]); + + // 컴포넌트 마운트 시 초기 데이터 로드 + useEffect(() => { + if (roomId && !hasInitiallyLoaded) { + loadMessages(undefined, true); + } + }, [roomId, hasInitiallyLoaded]); + + // 무한 스크롤 처리 + useEffect(() => { + const handleScroll = () => { + const { scrollTop, scrollHeight, clientHeight } = document.documentElement; + + // 스크롤이 하단 근처에 도달했을 때 더 많은 데이터 로드 + if (scrollTop + clientHeight >= scrollHeight - 100 && !isLoadingMore && !isLast && hasInitiallyLoaded) { + loadMoreMessages(); + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [loadMoreMessages, isLoadingMore, isLast, hasInitiallyLoaded]); + const handleSendMessage = useCallback(async () => { if (inputValue.trim() === '' || isSubmitting) return; @@ -41,6 +183,16 @@ const TodayWords = () => { return; } + // 6개 작성 시도 시 토스트로 차단 + if (todayMyMessageCount >= DAILY_LIMIT) { + openSnackbar({ + message: '오늘의 한마디는 하루에 다섯번까지 작성할 수 있어요', + variant: 'top', + onClose: () => {}, + }); + return; + } + setIsSubmitting(true); try { @@ -48,36 +200,29 @@ const TodayWords = () => { const response = await createDailyGreeting(parseInt(roomId), inputValue.trim()); if (response.isSuccess) { - // 성공 시 새 메시지 생성 - const now = new Date(); - const newMessage: Message = { - id: response.data.attendanceCheckId.toString(), - user: 'user.01', // TODO: 실제 사용자 정보로 변경 - content: inputValue.trim(), - timestamp: now - .toLocaleDateString('ko-KR', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - }) - .replace(/\. /g, '.') - .replace(/\.$/, ''), - timeAgo: '방금 전', - createdAt: now, - }; - - // 실제 messages 상태에 추가 - setMessages(prevMessages => [...prevMessages, newMessage]); - // 입력 필드 초기화 setInputValue(''); - // 성공 메시지 표시 - openSnackbar({ - message: '오늘의 한마디가 작성되었습니다.', - variant: 'top', - onClose: () => {}, - }); + // 최신 목록 다시 불러오기 위해 상태 초기화 + setMessages([]); + setNextCursor(null); + setIsLast(false); + setHasInitiallyLoaded(false); + + // 5개 도달 시 흰색 토스트, 아니면 일반 성공 메시지 + if (todayMyMessageCount + 1 >= DAILY_LIMIT) { + openSnackbar({ + message: '오늘의 한마디는 하루에 다섯번까지 작성할 수 있어요', + variant: 'top', + onClose: () => {}, + }); + } else { + openSnackbar({ + message: '오늘의 한마디가 작성되었습니다.', + variant: 'top', + onClose: () => {}, + }); + } // 자동으로 스크롤을 아래로 이동 setTimeout(() => { @@ -107,14 +252,14 @@ const TodayWords = () => { }; }; - if (axiosError.response?.data?.message) { - errorMessage = axiosError.response.data.message; - } else if (axiosError.response?.data?.code === 400) { - errorMessage = '오늘의 한마디 작성 가능 횟수를 초과했습니다.'; + if (axiosError.response?.data?.code === 400) { + errorMessage = '오늘의 한마디는 하루에 다섯번까지 작성할 수 있어요'; } else if (axiosError.response?.data?.code === 403) { errorMessage = '방 접근 권한이 없습니다.'; } else if (axiosError.response?.data?.code === 404) { errorMessage = '존재하지 않는 방입니다.'; + } else if (axiosError.response?.data?.message) { + errorMessage = axiosError.response.data.message; } } @@ -126,35 +271,10 @@ const TodayWords = () => { } finally { setIsSubmitting(false); } - }, [inputValue, roomId, isSubmitting, openSnackbar]); - - // 더미 모드에서 메시지 전송 처리 (개발용) - const handleDummySendMessage = useCallback(() => { - if (inputValue.trim() === '') return; - - if (messageListRef.current) { - messageListRef.current.addMessage(inputValue.trim()); - } - setInputValue(''); + }, [inputValue, roomId, isSubmitting, openSnackbar, todayMyMessageCount, DAILY_LIMIT]); - // 자동으로 스크롤을 아래로 이동 - setTimeout(() => { - window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); - }, 100); - }, [inputValue]); - // 최종 메시지 전송 핸들러 - const finalHandleSendMessage = showMessages ? handleDummySendMessage : handleSendMessage; - // MessageList에서 메시지가 삭제되었을 때 호출될 콜백 - const handleMessageDelete = (messageId: string) => { - if (!showMessages) { - setMessages(prevMessages => prevMessages.filter(message => message.id !== messageId)); - } - }; - - // 실제 메시지가 있으면 실제 메시지를, 더미 모드면 더미 메시지를 표시 - const displayMessages = showMessages ? dummyMessages : messages; return ( <> @@ -165,50 +285,34 @@ const TodayWords = () => { /> - {displayMessages.length === 0 ? ( + {isLoading && !hasInitiallyLoaded ? ( +
+ +
+ ) : messages.length === 0 ? ( ) : ( - + <> + + {isLoadingMore && ( +
+ +
+ )} + )}
- - {/* 개발용 토글 버튼 */} -
); diff --git a/src/stores/usePopupStore.ts b/src/stores/usePopupStore.ts index 8416e825..9d203af1 100644 --- a/src/stores/usePopupStore.ts +++ b/src/stores/usePopupStore.ts @@ -16,6 +16,8 @@ export interface MoreMenuProps { onDelete?: () => void; onClose?: () => void; onReport?: () => void; + isWriter?: boolean; + type?: 'post' | 'reply'; } export interface SnackbarProps { diff --git a/src/types/today.ts b/src/types/today.ts index 758d0f51..ba9cc656 100644 --- a/src/types/today.ts +++ b/src/types/today.ts @@ -5,4 +5,24 @@ export interface Message { timestamp: string; timeAgo: string; createdAt: Date; + profileImageUrl?: string; + isWriter?: boolean; +} + +// 오늘의 한마디 관련 타입들 +export interface TodayCommentItem { + attendanceCheckId: number; + creatorId: number; + creatorNickname: string; + creatorProfileImageUrl: string; + todayComment: string; + postDate: string; + date: string; + isWriter: boolean; +} + +export interface DailyGreetingData { + todayCommentList: TodayCommentItem[]; + nextCursor: string; + isLast: boolean; }