Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/review #74

Merged
merged 3 commits into from
Jan 30, 2025
Merged
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
41 changes: 22 additions & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Redirect from '@/routes/Redirect';

import CompleteProfilePage from './components/home/Topbar/Menu/CompleteProfilePage';
import RegisterPage from './components/home/Topbar/Menu/RegisterPage';
import { ReviewProvider } from './components/roomdetail/ReviewContext';
import MyReservations from './routes/MyReservations';
import MyReviews from './routes/MyReviews';
import ProfileEdit from './routes/ProfileEdit';
Expand All @@ -22,25 +23,27 @@ export const App = () => {
// useSearch ํ›…์„ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ ๋†จ์Šต๋‹ˆ๋‹ค. AppProvider ๋“ฑ์˜ ๋‹ค๋ฅธ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ์˜ฎ๊ฒจ๋‘๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
<StyledEngineProvider injectFirst>
<SearchProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="complete-profile" element={<CompleteProfilePage />} />
<Route path="/:id" element={<Roomdetail />} />
<Route path="/tests" element={<ApiTest />} />
<Route path="/redirect" element={<Redirect />} />
<Route path="/hosting" element={<Hosting />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/profile/edit" element={<ProfileEdit />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/MyReservations" element={<MyReservations />} />
<Route path="/MyReviews" element={<MyReviews />} />
<Route path="/reviews/:reservationId" element={<Review />} />
<Route
path="/reservations/:reservationId"
element={<ReservationDetails />}
/>
<Route path="*" element={<h1>404 Not Found</h1>} />
</Routes>
<ReviewProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="complete-profile" element={<CompleteProfilePage />} />
<Route path="/:id" element={<Roomdetail />} />
<Route path="/tests" element={<ApiTest />} />
<Route path="/redirect" element={<Redirect />} />
<Route path="/hosting" element={<Hosting />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/profile/edit" element={<ProfileEdit />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/MyReservations" element={<MyReservations />} />
<Route path="/MyReviews" element={<MyReviews />} />
<Route path="/reviews/:reservationId" element={<Review />} />
<Route
path="/reservations/:reservationId"
element={<ReservationDetails />}
/>
<Route path="*" element={<h1>404 Not Found</h1>} />
</Routes>
</ReviewProvider>
</SearchProvider>
</StyledEngineProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/roomdetail/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const Info = ({ data }: InfoProps) => {
<div className="col-span-1 row-span-1 flex gap-2 items-center px-4">
<img src={LuggageIcon} className="h-6 w-6 opacity-60" />
<div className="opacity-60 text-sm">
{isluggage ? '์—ฌํ–‰ ๊ฐ€๋ฐฉ ๋ณด๊ด€ ๊ฐ€๋Šฅ' : '์—ฌํ–‰ ๊ฐ€๋ฐฉ ๋ณด๊ด€ ํ’€๊ฐ€'}
{isluggage ? '์—ฌํ–‰ ๊ฐ€๋ฐฉ ๋ณด๊ด€ ๊ฐ€๋Šฅ' : '์—ฌํ–‰ ๊ฐ€๋ฐฉ ๋ณด๊ด€ ๋ถˆ๊ฐ€'}
</div>
</div>
<div className="col-span-1 row-span-1 flex gap-2 items-center px-4">
Expand Down
119 changes: 89 additions & 30 deletions src/components/roomdetail/PhotoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import { useEffect } from 'react';

import { Shareheart } from '@/components/roomdetail/Shareheart';

const PhotoModal = ({ onClose }: { onClose: () => void }) => {
const photos = [
{ title: '๊ฑฐ์‹ค', icon: <PhotoSizeSelectActualIcon /> },
{ title: '์นจ์‹ค', icon: <PhotoSizeSelectActualIcon /> },
{ title: '์š•์‹ค', icon: <PhotoSizeSelectActualIcon /> },
{ title: '์™ธ๋ถ€', icon: <PhotoSizeSelectActualIcon /> },
{ title: '์ถ”๊ฐ€ ์‚ฌ์ง„', icon: <PhotoSizeSelectActualIcon /> },
];
type Props = {
onClose: () => void;
UrlList: string[];
};

const PhotoModal = ({ onClose, UrlList }: Props) => {
useEffect(() => {
document.body.style.overflow = 'hidden'; // ๋ฐฐ๊ฒฝ ์Šคํฌ๋กค ๋น„ํ™œ์„ฑํ™”
return () => {
Expand Down Expand Up @@ -45,34 +42,96 @@ const PhotoModal = ({ onClose }: { onClose: () => void }) => {

{/* ์‚ฌ์ง„ ๋ชฉ๋ก */}
<div className="grid grid-cols-5 gap-4 mb-12 p-6">
{photos.map((photo, index) => (
<div
key={index}
className="flex flex-col items-center justify-center space-y-2 cursor-pointer"
>
<div className="w-full h-24 bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md">
{photo.icon}
{UrlList.length > 0 ? (
<>
{UrlList.map((url, index) => (
<div
key={index}
className="w-full flex flex-col items-center justify-center space-y-2 cursor-pointer"
>
<div className="flex items-center justify-center bg-gray-200 w-full shadow-md rounded-sm">
<img
className="w-fit h-28"
src={url}
alt={`์‚ฌ์ง„ ${index}`}
/>
</div>
<span className="text-sm text-gray-700">{index}</span>
</div>
))}
{Array.from({ length: Math.max(5 - UrlList.length, 0) }).map(
(_, index) => (
<div
key={index}
className="w-full flex flex-col items-center justify-center space-y-2 cursor-pointer"
>
<div className="flex items-center justify-center bg-gray-200 w-full shadow-md rounded-sm">
<PhotoSizeSelectActualIcon className="w-fit h-28 text-white" />
</div>
<span className="text-sm text-gray-700">์ •๋ณด์—†์Œ</span>
</div>
),
)}
</>
) : (
Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className="flex flex-col items-center justify-center space-y-2 cursor-pointer"
>
<PhotoSizeSelectActualIcon className="w-full h-24 bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md" />
<span className="text-sm text-gray-700">{index}</span>
</div>
<span className="text-sm text-gray-700">{photo.title}</span>
</div>
))}
))
)}
</div>

{/* ์„ ํƒํ•œ ์‚ฌ์ง„ ๋ณด๊ธฐ */}
<div className="flex flex-col gap-4 mb-6 p-6">
{photos.map((photo, index) => (
<div
key={index}
className="flex items-start space-y-2 gap-2 cursor-pointer"
>
<span className="text-sm flex-[30%] w-fit text-gray-700">
{photo.title}
</span>
<div className="flex-[70%] w-fit h-[500px] bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md">
{photo.icon}
{UrlList.length > 0 ? (
<>
{UrlList.map((url, index) => (
<div
key={index}
className="flex items-start space-y-2 gap-2 cursor-pointer"
>
<span className="text-sm flex-[30%] w-fit text-gray-700">
{index}
</span>
<img
src={url}
className="flex-[70%] w-[500px] h-fit bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md"
alt={`์„ ํƒํ•œ ์‚ฌ์ง„ ${index}`}
/>
</div>
))}
{Array.from({ length: Math.max(5 - UrlList.length, 0) }).map(
(_, index) => (
<div
key={index}
className="flex items-start space-y-2 gap-2 cursor-pointer"
>
<span className="text-sm flex-[30%] w-fit text-gray-700">
์ •๋ณด์—†์Œ
</span>
<PhotoSizeSelectActualIcon className="flex-[70%] w-[500px] h-fit bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md" />
</div>
),
)}
</>
) : (
Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className="flex items-start space-y-2 gap-2 cursor-pointer"
>
<span className="text-sm flex-[30%] w-fit text-gray-700">
์ •๋ณด์—†์Œ
</span>
<PhotoSizeSelectActualIcon className="flex-[70%] w-[500px] h-fit bg-gray-300 text-white flex items-center justify-center rounded-sm shadow-md" />
</div>
</div>
))}
))
)}
</div>
</div>
</div>
Expand Down
20 changes: 6 additions & 14 deletions src/components/roomdetail/Reservation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import FlagIcon from '@mui/icons-material/Flag';
import { useState } from 'react';

import clock from '@/assets/icons/reservation/clock.svg';
import axiosInstance from '@/axiosInstance';
import BaseModal from '@/components/common/Modal/BaseModal';
import { useSearch } from '@/components/home/context/SearchContext';
import RoomGuestsModal from '@/components/roomdetail/RoomGuestsModal';
Expand Down Expand Up @@ -49,22 +50,13 @@ const Reservation = ({ data }: InfoProps) => {
numberOfGuests: guests,
};

const response = await fetch('/api/v1/reservations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(sendData),
});
const response = await axiosInstance.post<roomReservationResponseType>(
'/api/v1/reservations',
sendData,
);

console.debug(sendData);

if (!response.ok) {
throw new Error('์ˆ™์†Œ ์˜ˆ์•ฝ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
const responseData =
(await response.json()) as roomReservationResponseType;
const responseData = response.data;
alert(
`์ˆ™์†Œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์˜ˆ์•ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ID: ${responseData.reservationId}`,
);
Expand Down
87 changes: 87 additions & 0 deletions src/components/roomdetail/ReviewContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import axios from 'axios';
import type { ReactNode } from 'react';
import { createContext, useContext, useState } from 'react';
import { useCallback } from 'react';

import type { ReviewsResponse } from '@/types/reviewType';
import type { roomType } from '@/types/roomType';

type ReviewContextType = {
isLoading: boolean;
error: string | null;
initReviews: (
data: roomType,
selectedOption: string,
page: number,
) => Promise<void>;
reviewData: ReviewsResponse | null;
};

const ReviewContext = createContext<ReviewContextType | undefined>(undefined);

export function ReviewProvider({ children }: { children: ReactNode }) {
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [reviewData, setReviewData] = useState<ReviewsResponse | null>(null);

// ์ดˆ๊ธฐ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜
const initReviews = useCallback(
async (data: roomType, selectedOption: string, page: number) => {
try {
setIsLoading(true);
setError(null);

const size = 4;
let sort = '';

if (selectedOption === '์ตœ์‹ ์ˆœ') {
sort = 'createdAt,desc';
} else if (selectedOption === '์˜ค๋ž˜๋œ์ˆœ') {
sort = 'rating,asc';
} else if (selectedOption === '๋†’์€ ํ‰์ ์ˆœ') {
sort = 'rating,desc';
} else if (selectedOption === '๋‚ฎ์€ ํ‰์ ์ˆœ') {
sort = 'rating,asc';
}

const url = `/api/v1/reviews/room/${data.roomId}?page=${page}&size=${size}&sort=${sort}`;

const response = await axios.get<ReviewsResponse>(url);

const responseData = response.data;

setReviewData(responseData);

console.debug(responseData, page);
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : '์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.';
setError(errorMessage);
} finally {
setIsLoading(false);
}
},
[],
);

return (
<ReviewContext.Provider
value={{
initReviews,
reviewData,
isLoading,
error,
}}
>
{children}
</ReviewContext.Provider>
);
}

export function useReview() {
const context = useContext(ReviewContext);
if (context === undefined) {
throw new Error('useReview must be used within a ReviewProvider');
}
return context;
}
Loading