Skip to content
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@emotion/css": "^11.13.5",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@tanstack/react-query": "^5.90.21",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if `@tanstack/react-query` is imported anywhere in the codebase
rg '@tanstack/react-query' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json'

Repository: THIP-TextHip/THIP-Web

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check if `@tanstack/react-query` is imported anywhere in the codebase
rg '@tanstack/react-query' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' -g '!node_modules'

Repository: THIP-TextHip/THIP-Web

Length of output: 47


@tanstack/react-query는 코드베이스에서 사용되지 않습니다.

이 패키지는 package.json에 추가되었지만 어떤 파일에서도 import되거나 사용되지 않습니다. 낙관적 업데이트 패턴이 모두 수동 상태 관리(useState/useRef)로 구현되어 있으므로, 이 의존성을 제거하거나 실제로 사용하는 코드를 추가해야 합니다. 향후 사용 예정인 경우 별도 PR에서 처리하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 19, package.json contains an unused dependency
"@tanstack/react-query"; remove the dependency line for "@tanstack/react-query"
from package.json (or alternatively add the actual imports/usages where you
intend to use it), run yarn/npm install to update lockfile, and ensure no code
references "@tanstack/react-query" (search for imports of react-query or
useQuery/useMutation) before merging; if you plan to add optimistic update logic
later, move the dependency change into a separate PR.

"@types/react-datepicker": "^7.0.0",
"axios": "^1.11.0",
"react": "^19.1.0",
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 71 additions & 29 deletions src/components/common/Post/PostFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import like from '../../../assets/feed/like.svg';
import activeLike from '../../../assets/feed/activeLike.svg';
import comment from '../../../assets/feed/comment.svg';
Expand All @@ -7,6 +7,7 @@ import activeSave from '../../../assets/feed/activeSave.svg';
import lockIcon from '../../../assets/feed/lockIcon.svg';
import { postSaveFeed } from '@/api/feeds/postSave';
import { postFeedLike } from '@/api/feeds/postFeedLike';
import { usePreventDoubleClick } from '@/hooks/usePreventDoubleClick';
import { Container } from './PostFooter.styled';

interface PostFooterProps {
Expand Down Expand Up @@ -36,39 +37,75 @@ const PostFooter = ({
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);
const [saved, setSaved] = useState(isSaved);

const handleLike = async () => {
try {
const response = await postFeedLike(feedId, !liked);
const likedRef = useRef(isLiked);
const savedRef = useRef(isSaved);
const { isLoading: isLikeLoading, run: runLike } = usePreventDoubleClick();
const { isLoading: isSaveLoading, run: runSave } = usePreventDoubleClick();

if (response.isSuccess) {
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
console.log('좋아요 상태 변경 성공:', response.data.isLiked);
} else {
console.error('좋아요 상태 변경 실패:', response.message);
useEffect(() => {
setLiked(isLiked);
likedRef.current = isLiked;
}, [isLiked]);

useEffect(() => {
setSaved(isSaved);
savedRef.current = isSaved;
}, [isSaved]);

const handleLike = () => {
runLike(async () => {
const nextLiked = !likedRef.current;
likedRef.current = nextLiked;
setLiked(nextLiked);
setLikeCount(prev => (nextLiked ? prev + 1 : prev - 1));

await new Promise(resolve => setTimeout(resolve, 300));

try {
const response = await postFeedLike(feedId, nextLiked);
if (!response.isSuccess && likedRef.current === nextLiked) {
const rollbackState = !nextLiked;
likedRef.current = rollbackState;
setLiked(rollbackState);
setLikeCount(prev => (nextLiked ? prev - 1 : prev + 1));
}
} catch {
if (likedRef.current === nextLiked) {
const rollbackState = !nextLiked;
likedRef.current = rollbackState;
setLiked(rollbackState);
setLikeCount(prev => (nextLiked ? prev - 1 : prev + 1));
}
}
} catch (error) {
console.error('좋아요 API 호출 실패:', error);
}
});
};

const handleSave = async () => {
try {
const response = await postSaveFeed(feedId, !saved);
const handleSave = () => {
runSave(async () => {
const nextSaved = !savedRef.current;
savedRef.current = nextSaved;
setSaved(nextSaved);
onSaveToggle?.(feedId, nextSaved);

if (response.isSuccess) {
const newSaveState = response.data?.isSaved ?? !saved;
setSaved(newSaveState);
console.log('저장 상태 변경 성공:', newSaveState);
if (onSaveToggle) {
onSaveToggle(feedId, newSaveState);
await new Promise(resolve => setTimeout(resolve, 300));

try {
const response = await postSaveFeed(feedId, nextSaved);
if (!response.isSuccess && savedRef.current === nextSaved) {
const rollbackState = !nextSaved;
savedRef.current = rollbackState;
setSaved(rollbackState);
onSaveToggle?.(feedId, rollbackState);
}
} catch {
if (savedRef.current === nextSaved) {
const rollbackState = !nextSaved;
savedRef.current = rollbackState;
setSaved(rollbackState);
onSaveToggle?.(feedId, rollbackState);
}
} else {
console.error('저장 상태 변경 실패:', response.message);
}
} catch (error) {
console.error('저장 API 호출 실패:', error);
}
});
};

const handleComment = () => {
Expand All @@ -80,7 +117,7 @@ const PostFooter = ({
<Container isDetail={isDetail}>
<div className="left">
<div className="count">
<img src={liked ? activeLike : like} onClick={handleLike} />
<img src={liked ? activeLike : like} onClick={handleLike} style={{ opacity: isLikeLoading ? 0.6 : 1 }} />
<div>{likeCount}</div>
</div>
<div className="count comment">
Expand All @@ -96,7 +133,12 @@ const PostFooter = ({
<img src={lockIcon} alt="비공개" />
)
) : (
<img src={saved ? activeSave : save} onClick={handleSave} alt="저장" />
<img
src={saved ? activeSave : save}
onClick={handleSave}
alt="저장"
style={{ opacity: isSaveLoading ? 0.6 : 1 }}
/>
)}
</div>
</Container>
Expand Down
40 changes: 29 additions & 11 deletions src/components/common/Post/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import like from '../../../assets/feed/like.svg';
import activeLike from '../../../assets/feed/activeLike.svg';
import { useReplyActions } from '@/hooks/useReplyActions';
import { usePopupActions } from '@/hooks/usePopupActions';
import { usePreventDoubleClick } from '@/hooks/usePreventDoubleClick';
import { postLike } from '@/api/comments/postLike';
import { deleteComment } from '@/api/comments/deleteComment';
import { DeletedContainer, Container, ReplySection } from './Reply.styled';
Expand Down Expand Up @@ -32,27 +33,43 @@ const Reply = ({
const [liked, setLiked] = useState(isLike);
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);
const containerRef = useRef<HTMLDivElement>(null);
const { isLoading: isLikeLoading, run: runLike } = usePreventDoubleClick();

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

const handleLike = async () => {
try {
const response = await postLike(commentId, !liked);
const handleLike = () => {
runLike(async () => {
const previousLiked = liked;
const previousLikeCount = likeCount;
const nextLiked = !liked;

if (response.isSuccess) {
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
} else {
setLiked(nextLiked);
setLikeCount(prev => (nextLiked ? prev + 1 : prev - 1));

await new Promise(resolve => setTimeout(resolve, 300));

try {
const response = await postLike(commentId, nextLiked);
if (!response.isSuccess) {
setLiked(previousLiked);
setLikeCount(previousLikeCount);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch {
setLiked(previousLiked);
setLikeCount(previousLikeCount);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
message: '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
console.error('좋아요 상태 변경 실패:', error);
}
});
};

const handleReplyClick = () => {
Expand Down Expand Up @@ -163,6 +180,7 @@ const Reply = ({
handleLike();
}}
alt="좋아요"
style={{ opacity: isLikeLoading ? 0.6 : 1 }}
/>
<div className="count">{likeCount}</div>
</div>
Expand Down
41 changes: 29 additions & 12 deletions src/components/common/Post/SubReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import activeLike from '../../../assets/feed/activeLike.svg';
import replyIcon from '../../../assets/feed/replyIcon.svg';
import { useReplyActions } from '@/hooks/useReplyActions';
import { usePopupActions } from '@/hooks/usePopupActions';
import { usePreventDoubleClick } from '@/hooks/usePreventDoubleClick';
import { postLike } from '@/api/comments/postLike';
import { deleteComment } from '@/api/comments/deleteComment';
import { DeletedContainer, Container, ReplyIcon, Content, ReplySection } from './SubReply.styled';
Expand Down Expand Up @@ -34,6 +35,7 @@ const SubReply = ({
const [liked, setLiked] = useState<boolean>(isLike);
const [currentLikeCount, setCurrentLikeCount] = useState<number>(likeCount);
const containerRef = useRef<HTMLDivElement>(null);
const { isLoading: isLikeLoading, run: runLike } = usePreventDoubleClick();

const { startReply } = useReplyActions();
const { openMoreMenu, closePopup, openSnackbar } = usePopupActions();
Expand All @@ -42,24 +44,38 @@ const SubReply = ({
startReply(creatorNickname, commentId);
};

const handleLike = async () => {
try {
const response = await postLike(commentId, !liked);
const handleLike = () => {
runLike(async () => {
const previousLiked = liked;
const previousLikeCount = currentLikeCount;
const nextLiked = !liked;

if (response.isSuccess) {
setLiked(response.data.isLiked);
setCurrentLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
} else {
console.error('좋아요 상태 변경 실패:', response.message);
setLiked(nextLiked);
setCurrentLikeCount(prev => (nextLiked ? prev + 1 : prev - 1));

await new Promise(resolve => setTimeout(resolve, 300));

try {
const response = await postLike(commentId, nextLiked);
if (!response.isSuccess) {
setLiked(previousLiked);
setCurrentLikeCount(previousLikeCount);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch {
setLiked(previousLiked);
setCurrentLikeCount(previousLikeCount);
openSnackbar({
message: response.message || '좋아요 처리 중 오류가 발생했습니다.',
message: '좋아요 처리 중 오류가 발생했습니다.',
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
console.error('좋아요 상태 변경 실패:', error);
}
});
};

const handleDelete = async () => {
Expand Down Expand Up @@ -170,6 +186,7 @@ const SubReply = ({
handleLike();
}}
alt="좋아요"
style={{ opacity: isLikeLoading ? 0.6 : 1 }}
/>
<div className="count">{currentLikeCount}</div>
</div>
Expand Down
Loading