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
34 changes: 34 additions & 0 deletions src/api/event/short-review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,37 @@ export const deleteShortReview = async (
throw err;
}
};

interface ShortReviewReactionResponse {
isSuccess: boolean;
message?: string;
result: {
likeCount: number;
dislikeCount: number;
isLiked: boolean;
isDisliked: boolean;
};
}

export const toggleShortReviewReaction = async (
reviewId: number,
reactionType: 0 | 1,
): Promise<ShortReviewReactionResponse> => {
try {
// 객체가 아닌 reactionType 값만 직접 전송
const response = await instance.post<ShortReviewReactionResponse>(
`/events/short-reviews/${reviewId}/reaction`,
reactionType,
);
return response.data;
} catch (err: any) {
// 타입을 any로 명시
console.error('Error toggling short review reaction:', err);

// 에러 응답 로깅 추가 (타입 가드 사용)
if (err.response) {
console.error('Error response:', err.response.data);
}
throw err;
}
};
51 changes: 36 additions & 15 deletions src/api/review/WriteReview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,47 @@ export const writeReview = async (
images?: File[],
): Promise<WriteReviewResponse> => {
try {
const formData = new FormData();
// 먼저 이미지 업로드 처리
let imageUrls: string[] = [];

// JSON 데이터를 request 키로 추가
formData.append(
'request',
new Blob([JSON.stringify(reviewData)], { type: 'application/json' }),
);

// 이미지 파일들 추가
if (images?.length) {
images.forEach((image) => {
formData.append('image', image);
});
imageUrls = await Promise.all(
images.map(async (image) => {
const imageFormData = new FormData();
// 백엔드 요구사항에 맞게 폴더 정보 추가
imageFormData.append('folder', JSON.stringify({ folder: 'review' }));
imageFormData.append('image', image);

// instance에 이미 /api가 포함되어 있으므로 /images로 요청
const imageResponse = await instance.post('/images', imageFormData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});

// 이미지 URL 반환 (응답 구조에 맞게 수정 필요)
return imageResponse.data.result;
}),
);
}

const response = await instance.post<WriteReviewResponse>(REVIEW_API_ENDPOINT, formData, {
headers: {
'Content-Type': 'multipart/form-data',
// 이미지 URL을 reviewData에 추가
const updatedReviewData = {
...reviewData,
imageUrls: imageUrls, // 백엔드 API 요구사항에 맞게 필드명 조정
};

// 리뷰 데이터 전송 (REVIEW_API_ENDPOINT는 '/reviews'이므로 전체 경로는 '/api/reviews')
const response = await instance.post<WriteReviewResponse>(
REVIEW_API_ENDPOINT,
updatedReviewData,
{
headers: {
'Content-Type': 'application/json',
},
},
});
);

return response.data;
} catch (error) {
if (error instanceof AxiosError) {
Expand Down
4 changes: 1 addition & 3 deletions src/api/review/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ import instance from '@/api/axios';
import { PointBalanceResponse } from '@/types/review/point';

export const getPointBalance = () => {
return instance
.get<PointBalanceResponse>('/api/points/balance')
.then((response) => response.data);
return instance.get<PointBalanceResponse>('/points/balance').then((response) => response.data);
};
Empty file added src/api/review/purchase.ts
Empty file.
116 changes: 86 additions & 30 deletions src/hooks/event/useEventPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { useState, useEffect } from 'react';
import { EventDetails } from '@/types/event/details';
import { EventShortReview } from '@/types/event/short-review';
import { getEventDetails } from '@/api/event/details';
import { createShortReview, updateShortReview, deleteShortReview } from '@/api/event/short-review';
import {
createShortReview,
updateShortReview,
deleteShortReview,
toggleShortReviewReaction,
} from '@/api/event/short-review';

// EventDetails hook (기존 코드 동일)
export const useEventDetails = (eventId: number) => {
Expand Down Expand Up @@ -45,6 +50,7 @@ export const useReviews = (initialReviews: EventShortReview[]) => {
const [editText, setEditText] = useState('');
const [editRating, setEditRating] = useState(0);
const [editError, setEditError] = useState<string | null>(null);
const [isReactionLoading, setIsReactionLoading] = useState(false);

const handleEditStart = (review: EventShortReview) => {
if (!review || typeof review.id !== 'number') {
Expand Down Expand Up @@ -120,37 +126,86 @@ export const useReviews = (initialReviews: EventShortReview[]) => {
}
};

const handleLike = (reviewId: number) => {
setReviews(
reviews.map((review) => {
if (review.id === reviewId) {
const newLikes = review.userVote === 'like' ? review.likes - 1 : review.likes + 1;
return {
...review,
likes: newLikes,
userVote: review.userVote === 'like' ? null : 'like',
};
}
return review;
}),
);
const handleLike = async (reviewId: number, isLoggedIn: boolean) => {
if (!isLoggedIn) return;
if (isReactionLoading) return;

try {
setIsReactionLoading(true);

// 좋아요는 reactionType: 1로 설정
const response = await toggleShortReviewReaction(reviewId, 1);

if (response.isSuccess) {
setReviews((prevReviews) =>
prevReviews.map((review) => {
if (review.id === reviewId) {
// API 응답에 따라 좋아요/싫어요 상태와 개수를 업데이트
return {
...review,
likes: response.result.likeCount,
dislikes: response.result.dislikeCount,
isLiked: response.result.isLiked,
isDisliked: response.result.isDisliked,
userVote: response.result.isLiked
? 'like'
: response.result.isDisliked
? 'dislike'
: null,
};
}
return review;
}),
);
} else {
console.error('Failed to toggle like:', response.message);
}
} catch (error) {
console.error('Error when liking review:', error);
} finally {
setIsReactionLoading(false);
}
};

const handleDislike = (reviewId: number) => {
setReviews(
reviews.map((review) => {
if (review.id === reviewId) {
const newDislikes =
review.userVote === 'dislike' ? review.dislikes - 1 : review.dislikes + 1;
return {
...review,
dislikes: newDislikes,
userVote: review.userVote === 'dislike' ? null : 'dislike',
};
}
return review;
}),
);
const handleDislike = async (reviewId: number, isLoggedIn: boolean) => {
if (!isLoggedIn) return;
if (isReactionLoading) return;

try {
setIsReactionLoading(true);

// 싫어요는 reactionType: 0으로 설정
const response = await toggleShortReviewReaction(reviewId, 0);

if (response.isSuccess) {
setReviews((prevReviews) =>
prevReviews.map((review) => {
if (review.id === reviewId) {
// API 응답에 따라 좋아요/싫어요 상태와 개수를 업데이트
return {
...review,
likes: response.result.likeCount,
dislikes: response.result.dislikeCount,
isLiked: response.result.isLiked,
isDisliked: response.result.isDisliked,
userVote: response.result.isLiked
? 'like'
: response.result.isDisliked
? 'dislike'
: null,
};
}
return review;
}),
);
} else {
console.error('Failed to toggle dislike:', response.message);
}
} catch (error) {
console.error('Error when disliking review:', error);
} finally {
setIsReactionLoading(false);
}
};

return {
Expand All @@ -168,6 +223,7 @@ export const useReviews = (initialReviews: EventShortReview[]) => {
handleDelete,
handleLike,
handleDislike,
isReactionLoading,
};
};

Expand Down
5 changes: 3 additions & 2 deletions src/pages/EventPage2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const EventPage = () => {
handleDelete,
handleLike,
handleDislike,
isReactionLoading,
} = useReviews([]);

const {
Expand Down Expand Up @@ -498,14 +499,14 @@ const EventPage = () => {
<S.FeedbackButtons>
<S.IconButton
onClick={() => handleLike(review.id, isLoggedIn)}
disabled={!isLoggedIn}
disabled={!isLoggedIn || isReactionLoading}
>
<ThumbsUp size={20} color={review.isLiked ? '#ffd700' : '#0c004b'} />
<span>{review.likes || 0}</span>
</S.IconButton>
<S.IconButton
onClick={() => handleDislike(review.id, isLoggedIn)}
disabled={!isLoggedIn}
disabled={!isLoggedIn || isReactionLoading}
>
<ThumbsDown size={20} color={review.isDisliked ? '#ffd700' : '#0c004b'} />
<span>{review.dislikes || 0}</span>
Expand Down
Empty file added src/types/review/purchase.ts
Empty file.