diff --git a/src/app/(with-header)/wines/[id]/_components/ReviewRating.tsx b/src/app/(with-header)/wines/[id]/_components/ReviewRating.tsx
index 6561e2a..1772f10 100644
--- a/src/app/(with-header)/wines/[id]/_components/ReviewRating.tsx
+++ b/src/app/(with-header)/wines/[id]/_components/ReviewRating.tsx
@@ -1,14 +1,16 @@
'use client';
import StaticRating from '@/components/StaticRating';
import PostReviewModal from '@/components/modal/PostReviewModal';
+import { AddReviewData } from './ReviewContainer';
type ReviewRatingProps = {
count: number;
ratingPercentages: number[];
avgRating: number;
+ addReview: (newReview: AddReviewData) => void;
};
-export default function ReviewRating({ count, avgRating, ratingPercentages }: ReviewRatingProps) {
+export default function ReviewRating({ count, avgRating, ratingPercentages, addReview }: ReviewRatingProps) {
return (
@@ -26,7 +28,7 @@ export default function ReviewRating({ count, avgRating, ratingPercentages }: Re
@@ -50,7 +52,7 @@ export default function ReviewRating({ count, avgRating, ratingPercentages }: Re
+
{}} isDraggable={isDraggable} name='바디감' size='large' />
{}} isDraggable={false} name='타닌' size='large' />
{}} isDraggable={false} name='당도' size='large' />
diff --git a/src/app/(with-header)/wines/[id]/_components/WineContainer.tsx b/src/app/(with-header)/wines/[id]/_components/WineContainer.tsx
index db7805e..6910694 100644
--- a/src/app/(with-header)/wines/[id]/_components/WineContainer.tsx
+++ b/src/app/(with-header)/wines/[id]/_components/WineContainer.tsx
@@ -1,52 +1,41 @@
'use client';
import { useState, useEffect } from 'react';
-import { useParams, useRouter } from 'next/navigation';
-import { fetchWithAuth } from '@/lib/auth';
-import WineCard, { WineCardProps } from '@/components/WineCard';
+import { useParams } from 'next/navigation';
+import { fetchWineDetail } from '@/lib/fetchWineDetail';
+import { ReviewData } from '@/types/review-data';
+import WineCard from '@/components/WineCard';
export default function WineContainer() {
const { id } = useParams();
- const [wine, setWine] = useState(null);
+ const [wine, setWine] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const router = useRouter();
useEffect(() => {
const fetchWineData = async () => {
- try {
- const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/wines/${id}`, {
- method: 'GET',
- });
-
- if (!response) {
- alert('로그인 후, 이용해 주세요');
- router.push('/signin');
+ if (id) {
+ const wineId = typeof id === 'string' ? Number(id) : NaN;
+ if (isNaN(wineId)) {
+ setError('유효한 와인 ID가 아닙니다.');
setLoading(false);
return;
}
- if (!response.ok) {
- if (response.status === 404) {
- alert('존재하지 않는 와인입니다. 다시 확인해 주세요.');
- router.push('/wines');
- }
+ try {
+ const wineData = await fetchWineDetail(wineId);
+ setWine(wineData);
+ } catch (error: unknown) {
+ if (error instanceof Error) setError(error.message);
+ } finally {
+ setLoading(false);
}
-
- const data: WineCardProps = await response.json();
- setWine(data);
- } catch (error: unknown) {
- if (error instanceof Error) setError(`와인 데이터 로드 실패: ${error.message}`);
- } finally {
+ } else {
setLoading(false);
}
};
- if (id) {
- fetchWineData();
- } else {
- setLoading(false);
- }
- }, [id, router]);
+ fetchWineData();
+ }, [id]);
if (loading) return Loading...
;
if (error) return {error}
;
diff --git a/src/components/InteractiveRating.tsx b/src/components/InteractiveRating.tsx
index 92aa3a5..e0ab048 100644
--- a/src/components/InteractiveRating.tsx
+++ b/src/components/InteractiveRating.tsx
@@ -20,6 +20,8 @@ export default function InteractiveRating({ initialValue = 0, onChange, size = '
setValue(newValue);
if (newValue !== null) {
onChange(newValue);
+ } else if (newValue === null) {
+ onChange(0);
}
}}
size={size}
diff --git a/src/components/modal/Modal.tsx b/src/components/modal/Modal.tsx
index 0bd8b0f..e246171 100644
--- a/src/components/modal/Modal.tsx
+++ b/src/components/modal/Modal.tsx
@@ -59,7 +59,7 @@ export default function Modal({ children, isOpen, setIsOpen, className }: ModalP
}
return createPortal(
-
@@ -140,10 +159,46 @@ export default function PatchReviewForm({ name, id, onClose }: postReviewPorp) {
와인의 맛은 어땠나요?
-
-
-
-
+
+
+
+
diff --git a/src/components/modal/PostReviewModal.tsx b/src/components/modal/PostReviewModal.tsx
index f780713..7e3fd3d 100644
--- a/src/components/modal/PostReviewModal.tsx
+++ b/src/components/modal/PostReviewModal.tsx
@@ -11,6 +11,7 @@ import close from '@/assets/icons/close.svg';
import wineIcon from '@/assets/icons/wine.svg';
import InteractiveRating from '../InteractiveRating';
import ControlBar from '../ControlBar';
+import { AddReviewData } from '@/app/(with-header)/wines/[id]/_components/ReviewContainer';
interface FormValues {
rating: number;
@@ -68,15 +69,16 @@ const INICIALVALUES = {
type: '',
};
-export default function PostReviewModal() {
+export default function PostReviewModal({ addReview }: { addReview: (newReview: AddReviewData) => void }) {
const [isOpen, setIsOpen] = useState(false);
const [wineData, setWineData] = useState(INICIALVALUES);
const [selectedAroma, setSelectedAroma] = useState([]);
const { id } = useParams<{ id: string }>();
const router = useRouter();
- const { register, handleSubmit, setValue, watch } = useForm({
+ const { register, handleSubmit, setValue, watch, reset } = useForm({
defaultValues: {
+ rating: 0,
lightBold: 0,
smoothTannic: 0,
drySweet: 0,
@@ -93,6 +95,17 @@ export default function PostReviewModal() {
};
const closeModal = () => {
+ setSelectedAroma([]);
+ reset({
+ rating: 0,
+ lightBold: 0,
+ smoothTannic: 0,
+ drySweet: 0,
+ softAcidic: 0,
+ aroma: [],
+ content: '',
+ wineId: wineData.id,
+ });
setIsOpen(false);
};
@@ -130,6 +143,41 @@ export default function PostReviewModal() {
const body = await response.json();
if (body) {
+ const newReview: AddReviewData = {
+ reviewId: body.id,
+ rating,
+ lightBold,
+ smoothTannic,
+ drySweet,
+ softAcidic,
+ aroma,
+ content,
+ user: {
+ id: body.user.id,
+ nickname: body.user.nickname,
+ image: body.user.image,
+ },
+ wineId,
+ wineName: wineData.name,
+ };
+ addReview(newReview);
+ reset({
+ rating: 0,
+ lightBold: 0,
+ smoothTannic: 0,
+ drySweet: 0,
+ softAcidic: 0,
+ aroma: [],
+ content: '',
+ wineId: wineData.id,
+ });
+ setSelectedAroma([]);
+ setValue('rating', -1);
+ setValue('lightBold', -1);
+ setValue('smoothTannic', -1);
+ setValue('drySweet', -1);
+ setValue('softAcidic', -1);
+ setValue('aroma', []);
setIsOpen(false);
}
} catch (error) {
diff --git a/src/lib/fetchWineDetail.ts b/src/lib/fetchWineDetail.ts
new file mode 100644
index 0000000..75a866c
--- /dev/null
+++ b/src/lib/fetchWineDetail.ts
@@ -0,0 +1,26 @@
+import { ReviewData } from '@/types/review-data';
+import { fetchWithAuth } from './auth';
+
+export const fetchWineDetail = async (id: number): Promise => {
+ try {
+ const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/wines/${id}`, {
+ method: 'GET',
+ });
+
+ if (!response) {
+ throw new Error('로그인 후, 이용해 주세요.');
+ }
+
+ if (!response.ok) {
+ throw new Error('와인 상세 정보를 불러오는 데 실패했습니다.');
+ }
+
+ const data: ReviewData = await response.json();
+ return data;
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ throw new Error(`와인 데이터 로드 실패: ${error.message}`);
+ }
+ throw new Error('알 수 없는 오류가 발생했습니다.');
+ }
+};
diff --git a/src/types/review-data.ts b/src/types/review-data.ts
index cf10f81..35ea67d 100644
--- a/src/types/review-data.ts
+++ b/src/types/review-data.ts
@@ -1,6 +1,11 @@
import { Wine } from './wine';
export interface ReviewData {
+ id: number;
+ name: string;
+ region: string;
+ image: string;
+ price: number;
avgRating: number;
reviews: {
id: number;
@@ -18,6 +23,7 @@ export interface ReviewData {
nickname: string;
image: string;
};
+ wine: Wine;
isLiked: boolean;
}[];
}