Skip to content
18 changes: 14 additions & 4 deletions src/api/comments/postLike.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface PostLikeRequest {
}

export interface PostLikeResponse {
isSuccess: boolean;
code: number;
status: string;
message: string;
Expand All @@ -15,8 +16,17 @@ export interface PostLikeResponse {
}

export const postLike = async (commentId: number, type: boolean) => {
const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
type,
});
return response.data;
try {
const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
type,
});
return response.data;
} catch (error: any) {
console.error('댓글 좋아요 API 오류:', error);
// 서버에서 에러 응답을 보낸 경우 해당 응답을 반환
if (error.response?.data) {
return error.response.data;
}
throw error;
}
};
13 changes: 11 additions & 2 deletions src/api/comments/postReply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export interface PostReplyResponse {
}

export const postReply = async (postId: number, request: PostReplyRequest) => {
const response = await apiClient.post<PostReplyResponse>(`/comments/${postId}`, request);
return response.data;
try {
const response = await apiClient.post<PostReplyResponse>(`/comments/${postId}`, request);
return response.data;
} catch (error: any) {
console.error('댓글 작성 API 오류:', error);
// 서버에서 에러 응답을 보낸 경우 해당 응답을 반환
if (error.response?.data) {
return error.response.data;
}
throw error;
}
};
13 changes: 11 additions & 2 deletions src/api/record/postVote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ export type VoteResponse = ApiResponse<VoteData>;

// 투표하기 API 함수
export const postVote = async (roomId: number, voteId: number, voteData: VoteRequest) => {
const response = await apiClient.post<VoteResponse>(`/rooms/${roomId}/vote/${voteId}`, voteData);
return response.data;
try {
const response = await apiClient.post<VoteResponse>(`/rooms/${roomId}/vote/${voteId}`, voteData);
return response.data;
} catch (error: any) {
console.error('투표 API 오류:', error);
// 서버에서 에러 응답을 보낸 경우 해당 응답을 반환
if (error.response?.data) {
return error.response.data;
}
throw error;
}
};

/*
Expand Down
6 changes: 5 additions & 1 deletion src/api/roomPosts/postRoomPostLike.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ export const postRoomPostLike = async (
requestData,
);
return response.data;
} catch (error) {
} catch (error: any) {
console.error('방 게시물 좋아요 API 오류:', error);
// 서버에서 에러 응답을 보낸 경우 해당 응답을 반환
if (error.response?.data) {
return error.response.data;
}
throw error;
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { useState, useEffect, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import MessageInput from '@/components/today-words/MessageInput';
import ReplyList from '@/components/common/Post/ReplyList';
import { getComments, type CommentData } from '@/api/comments/getComments';
import { postReply } from '@/api/comments/postReply';
import { useReplyActions } from '@/hooks/useReplyActions';
import { useReplyStore } from '@/stores/useReplyStore';
import { useCommentBottomSheetStore } from '@/stores/useCommentBottomSheetStore';
import { usePopupActions } from '@/hooks/usePopupActions';
import { getRoomPlaying } from '@/api/rooms/getRoomPlaying';
import { isRoomCompleted } from '@/utils/roomStatus';
import {
Overlay,
BottomSheet,
Expand All @@ -17,15 +21,18 @@ import {
} from './GlobalCommentBottomSheet.styled';

const GlobalCommentBottomSheet = () => {
const { roomId } = useParams<{ roomId: string }>();
const { isOpen, postId, postType, closeCommentBottomSheet } = useCommentBottomSheetStore();

const [commentList, setCommentList] = useState<CommentData[]>([]);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isSending, setIsSending] = useState(false);
const [roomCompleted, setRoomCompleted] = useState(false);

const { nickname, isReplying, cancelReply } = useReplyActions();
const { parentId } = useReplyStore();
const { openSnackbar } = usePopupActions();

// 댓글 목록 로드
const loadComments = useCallback(async () => {
Expand Down Expand Up @@ -62,15 +69,27 @@ const GlobalCommentBottomSheet = () => {
};

const response = await postReply(postId, requestData);

if (response.isSuccess) {
setInputValue('');
cancelReply(); // 답글 상태 초기화
// 댓글 목록 새로고침
await loadComments();
} else {
// 서버에서 받은 에러 메시지 표시
openSnackbar({
message: response.message || '댓글 작성 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
console.error('댓글 전송 실패:', error);
openSnackbar({
message: '네트워크 오류가 발생했습니다. 다시 시도해주세요.',
variant: 'top',
onClose: () => {},
});
} finally {
setIsSending(false);
}
Expand All @@ -88,6 +107,27 @@ const GlobalCommentBottomSheet = () => {
}
};

// 모임방 상태 확인
useEffect(() => {
const checkRoomStatus = async () => {
if (!roomId) return;

try {
const response = await getRoomPlaying(parseInt(roomId));
if (response.isSuccess) {
const completed = isRoomCompleted(response.data.progressEndDate);
setRoomCompleted(completed);
}
} catch (error) {
console.error('모임방 상태 확인 오류:', error);
}
};

if (isOpen) {
checkRoomStatus();
}
}, [isOpen, roomId]);

// 바텀시트가 열릴 때 댓글 로드
useEffect(() => {
if (isOpen) {
Expand Down Expand Up @@ -124,20 +164,22 @@ const GlobalCommentBottomSheet = () => {
)}
</Content>

<InputSection>
<MessageInput
placeholder={
isReplying ? `@${nickname}님에게 답글을 남겨보세요` : '댓글을 남겨보세요'
}
value={inputValue}
onChange={setInputValue}
onSend={handleSendComment}
isReplying={isReplying}
onCancelReply={handleCancelReply}
nickname={nickname}
disabled={isSending}
/>
</InputSection>
{!roomCompleted && (
<InputSection>
<MessageInput
placeholder={
isReplying ? `@${nickname}님에게 답글을 남겨보세요` : '댓글을 남겨보세요'
}
value={inputValue}
onChange={setInputValue}
onSend={handleSendComment}
isReplying={isReplying}
onCancelReply={handleCancelReply}
nickname={nickname}
disabled={isSending}
/>
</InputSection>
)}
</BottomSheet>
</Overlay>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/Modal/PopupContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ const PopupContainer = () => {
case 'confirm-modal':
return (
<Wrapper>
<ConfirmModal {...(popupProps as ConfirmModalProps)} />
<ConfirmModal {...(popupProps as ConfirmModalProps)} onClose={closePopup} />
</Wrapper>
);
case 'moremenu':
return <MoreMenu {...(popupProps as MoreMenuProps)} />;
return <MoreMenu {...(popupProps as MoreMenuProps)} onClose={closePopup} />;
case 'reply-modal':
return <ReplyModal {...(popupProps as ReplyModalProps)} />;
case 'snackbar':
Expand Down
16 changes: 13 additions & 3 deletions src/components/common/Post/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,19 @@ const Reply = ({
const handleLike = async () => {
try {
const response = await postLike(commentId, !liked);
console.log('좋아요 상태 변경 성공:', response);
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));

if (response.isSuccess) {
console.log('좋아요 상태 변경 성공:', response);
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
} else {
console.error('좋아요 상태 변경 실패:', response.message);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
console.error('좋아요 상태 변경 실패:', error);
}
Expand Down
17 changes: 13 additions & 4 deletions src/components/common/Post/SubReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,20 @@ const SubReply = ({
const handleLike = async () => {
try {
const response = await postLike(commentId, !liked);
console.log('좋아요 상태 변경 성공:', response);

// 서버 응답으로 상태 업데이트
setLiked(response.data.isLiked);
setCurrentLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
if (response.isSuccess) {
console.log('좋아요 상태 변경 성공:', response);
// 서버 응답으로 상태 업데이트
setLiked(response.data.isLiked);
setCurrentLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
} else {
console.error('좋아요 상태 변경 실패:', response.message);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
console.error('좋아요 상태 변경 실패:', error);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/TitleHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const TitleHeader = ({
{rightIcon}
</div>
) : rightButton ? (
<NextButton onClick={onRightClick} active={isNextActive}>
<NextButton onClick={isNextActive ? onRightClick : undefined} active={isNextActive}>
{rightButton}
</NextButton>
) : (
Expand Down
35 changes: 29 additions & 6 deletions src/components/group/HotTopicSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export interface Poll {
interface HotTopicSectionProps {
polls: Poll[];
hasPolls: boolean;
onClick: () => void;
onClick?: () => void;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

onClick prop이 사용되지 않음

props에서 onClick이 optional로 정의되어 있지만 컴포넌트 내부에서 실제로 사용되지 않습니다. 사용하지 않는 prop은 제거하는 것이 좋습니다.

interface HotTopicSectionProps {
  polls: Poll[];
  hasPolls: boolean;
-  onClick?: () => void;
  onPollClick: (pageNumber: number) => void;
}

Also applies to: 39-39

🤖 Prompt for AI Agents
In src/components/group/HotTopicSection.tsx around lines 35 and 39, the props
declare an optional onClick property but the component never uses it; remove
onClick from the prop type/interface (and any related prop forwarding) or, if
intended, wire it up to the correct clickable element (e.g., pass it to the root
button/div or list item) and ensure proper typing—update the component props
definition and usages accordingly to either eliminate the unused prop or attach
it to the interactive element.

onPollClick: (pageNumber: number) => void;
}

const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSectionProps) => {
const HotTopicSection = ({ polls, hasPolls, onPollClick }: HotTopicSectionProps) => {
const [currentPollIndex, setCurrentPollIndex] = useState(0);
const slideRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
Expand All @@ -46,17 +46,32 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect
const dragStateRef = useRef({
startX: 0,
startTranslateX: 0,
hasMoved: false,
});

const containerWidth = 100; // 각 슬라이드의 width %

// 투표 옵션 클릭 시 해당 페이지로 이동
const handleVoteClick = (poll: Poll) => {
if (!isDragging) {
const handleVoteClick = (e: React.MouseEvent | React.TouchEvent, poll: Poll) => {
e.stopPropagation();
if (!isDragging || !dragStateRef.current.hasMoved) {
onPollClick(poll.pageNumber);
}
};

// 터치 이벤트 핸들러
const handleVoteTouchEnd = (e: React.TouchEvent, poll: Poll) => {
e.preventDefault();
e.stopPropagation();
if (!isDragging || !dragStateRef.current.hasMoved) {
onPollClick(poll.pageNumber);
}
};

const handleVoteTouchStart = (e: React.TouchEvent) => {
e.stopPropagation();
};

// 슬라이드 위치 계산
const getTargetTranslateX = (index: number) => {
return -index * containerWidth;
Expand All @@ -81,6 +96,7 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect
setIsDragging(true);
dragStateRef.current.startX = clientX;
dragStateRef.current.startTranslateX = translateX;
dragStateRef.current.hasMoved = false;
},
[translateX],
);
Expand All @@ -91,6 +107,11 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect
if (!isDragging) return;

const deltaX = clientX - dragStateRef.current.startX;

if (Math.abs(deltaX) > 5) {
dragStateRef.current.hasMoved = true;
}

const newTranslateX =
dragStateRef.current.startTranslateX + (deltaX / window.innerWidth) * 100;

Expand Down Expand Up @@ -166,7 +187,9 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect
return poll.options.map((option, index) => (
<StyledVoteOption
key={option.id}
onClick={() => handleVoteClick(poll)}
onClick={e => handleVoteClick(e, poll)}
onTouchStart={handleVoteTouchStart}
onTouchEnd={e => handleVoteTouchEnd(e, poll)}
style={{ cursor: isDragging ? 'grabbing' : 'pointer' }}
>
<VoteOptionNumber>{index + 1}.</VoteOptionNumber>
Expand Down Expand Up @@ -228,7 +251,7 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect

return (
<StyledHotTopicSection>
<HotTopicSectionHeader onClick={onClick}>
<HotTopicSectionHeader>
<HotTopicSectionTitle>모임방의 뜨거운 감자</HotTopicSectionTitle>
</HotTopicSectionHeader>
{renderPollContent()}
Expand Down
Loading