diff --git a/frontend/src/api/reservation/reservationAPIV2.ts b/frontend/src/api/reservation/reservationAPIV2.ts index 01992108..9bb14321 100644 --- a/frontend/src/api/reservation/reservationAPIV2.ts +++ b/frontend/src/api/reservation/reservationAPIV2.ts @@ -1,5 +1,11 @@ import apiClient from '../apiClient'; -import type { PendingReservationCreateRequest, PendingReservationCreateResponse, ReservationDetailRetrieveResponse, ReservationSummaryRetrieveListResponse } from './reservationTypesV2'; +import type { + MostReservedThemeIdListResponse, + PendingReservationCreateRequest, + PendingReservationCreateResponse, + ReservationDetailRetrieveResponse, + ReservationSummaryRetrieveListResponse +} from './reservationTypesV2'; export const createPendingReservation = async (request: PendingReservationCreateRequest): Promise => { return await apiClient.post('/reservations/pending', request); @@ -21,3 +27,7 @@ export const fetchSummaryByMember = async (): Promise => { return await apiClient.get(`/reservations/${reservationId}/detail`); } + +export const fetchMostReservedThemeIds = async (count: number = 10): Promise => { + return await apiClient.get(`/reservations/popular-themes?count=${count}`, false); +} \ No newline at end of file diff --git a/frontend/src/api/reservation/reservationTypesV2.ts b/frontend/src/api/reservation/reservationTypesV2.ts index 3dc595fa..e028fe4c 100644 --- a/frontend/src/api/reservation/reservationTypesV2.ts +++ b/frontend/src/api/reservation/reservationTypesV2.ts @@ -1,5 +1,5 @@ -import type { MemberSummaryRetrieveResponse } from "@_api/member/memberTypes"; -import type { PaymentRetrieveResponse } from "@_api/payment/PaymentTypes"; +import type {MemberSummaryRetrieveResponse} from "@_api/member/memberTypes"; +import type {PaymentRetrieveResponse} from "@_api/payment/PaymentTypes"; export const ReservationStatusV2 = { PENDING: 'PENDING', @@ -56,3 +56,7 @@ export interface ReservationDetail { applicationDateTime: string; payment: PaymentRetrieveResponse; } + +export interface MostReservedThemeIdListResponse { + themeIds: string[]; +} \ No newline at end of file diff --git a/frontend/src/css/home-page-v2.css b/frontend/src/css/home-page-v2.css index d4b839a2..2728cb68 100644 --- a/frontend/src/css/home-page-v2.css +++ b/frontend/src/css/home-page-v2.css @@ -31,6 +31,7 @@ gap: 20px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; + cursor: pointer; } .theme-ranking-item-v2:hover { @@ -64,3 +65,101 @@ color: #505a67; margin: 0; } + +/* Modal Styles */ +.theme-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.theme-modal-content { + background-color: #ffffff; + padding: 30px; + border-radius: 16px; + width: 90%; + max-width: 600px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + gap: 20px; +} + +.modal-thumbnail { + width: 100%; + height: 250px; + object-fit: cover; + border-radius: 12px; +} + +.modal-theme-info h2 { + font-size: 26px; + font-weight: 700; + color: #333d4b; + margin: 0 0 10px; +} + +.modal-theme-info p { + font-size: 16px; + color: #505a67; + line-height: 1.6; + margin: 0 0 15px; +} + +.theme-details { + background-color: #f8f9fa; + border-radius: 8px; + padding: 15px; +} + +.theme-details p { + margin: 5px 0; + font-size: 15px; + color: #333d4b; +} + +.theme-details strong { + color: #191919; +} + +.modal-buttons { + display: flex; + gap: 15px; + margin-top: 15px; +} + +.modal-button { + flex: 1; + padding: 12px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} + +.modal-button.reserve { + background-color: #007bff; + color: white; +} + +.modal-button.reserve:hover { + background-color: #0056b3; +} + +.modal-button.close { + background-color: #6c757d; + color: white; +} + +.modal-button.close:hover { + background-color: #5a6268; +} diff --git a/frontend/src/pages/v2/HomePageV2.tsx b/frontend/src/pages/v2/HomePageV2.tsx index bad52d27..ed33b1ea 100644 --- a/frontend/src/pages/v2/HomePageV2.tsx +++ b/frontend/src/pages/v2/HomePageV2.tsx @@ -1,14 +1,31 @@ -import React, { useEffect, useState } from 'react'; -import { mostReservedThemes } from '../../api/theme/themeAPI'; -import '../../css/home-page-v2.css'; +import {fetchMostReservedThemeIds} from '@_api/reservation/reservationAPIV2'; +import '@_css/home-page-v2.css'; +import React, {useEffect, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import {findThemesByIds} from '../../api/theme/themeAPI'; +import {type UserThemeRetrieveResponse} from '../../api/theme/themeTypes'; const HomePageV2: React.FC = () => { - const [ranking, setRanking] = useState([]); + const [ranking, setRanking] = useState([]); + const [selectedTheme, setSelectedTheme] = useState(null); + const navigate = useNavigate(); useEffect(() => { const fetchData = async () => { try { - const response = await mostReservedThemes(10); + const themeIds = await fetchMostReservedThemeIds().then(res => { + const themeIds = res.themeIds; + if (themeIds.length === 0) { + setRanking([]); + return; + } + return themeIds; + }) + + if (themeIds === undefined) return; + if (themeIds.length === 0) return; + + const response = await findThemesByIds({ themeIds: themeIds }); setRanking(response.themes); } catch (err) { console.error('Error fetching ranking:', err); @@ -18,20 +35,56 @@ const HomePageV2: React.FC = () => { fetchData(); }, []); + const handleThemeClick = (theme: UserThemeRetrieveResponse) => { + setSelectedTheme(theme); + }; + + const handleCloseModal = () => { + setSelectedTheme(null); + }; + + const handleReservationClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (selectedTheme) { + navigate('/v2-1/reservation', { state: { themeId: selectedTheme.id } }); + } + }; + return (

인기 테마

{ranking.map(theme => ( -
- {theme.name} +
handleThemeClick(theme)}> + {theme.name}
{theme.name}
-

{theme.description}

))}
+ + {selectedTheme && ( +
+
e.stopPropagation()}> + {selectedTheme.name} +
+

{selectedTheme.name}

+

{selectedTheme.description}

+
+

난이도: {selectedTheme.difficulty}

+

가격: {selectedTheme.price.toLocaleString()}원

+

예상 시간: {selectedTheme.expectedMinutesFrom} ~ {selectedTheme.expectedMinutesTo}분

+

이용 가능 인원: {selectedTheme.minParticipants} ~ {selectedTheme.maxParticipants}명

+
+
+
+ + +
+
+
+ )}
); };