generated from pricelees/issue-pr-template
[#34] 회원 / 인증 도메인 재정의 #43
@ -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<PendingReservationCreateResponse> => {
|
||||
return await apiClient.post<PendingReservationCreateResponse>('/reservations/pending', request);
|
||||
@ -21,3 +27,7 @@ export const fetchSummaryByMember = async (): Promise<ReservationSummaryRetrieve
|
||||
export const fetchDetailById = async (reservationId: string): Promise<ReservationDetailRetrieveResponse> => {
|
||||
return await apiClient.get<ReservationDetailRetrieveResponse>(`/reservations/${reservationId}/detail`);
|
||||
}
|
||||
|
||||
export const fetchMostReservedThemeIds = async (count: number = 10): Promise<MostReservedThemeIdListResponse> => {
|
||||
return await apiClient.get<MostReservedThemeIdListResponse>(`/reservations/popular-themes?count=${count}`, false);
|
||||
}
|
||||
@ -56,3 +56,7 @@ export interface ReservationDetail {
|
||||
applicationDateTime: string;
|
||||
payment: PaymentRetrieveResponse;
|
||||
}
|
||||
|
||||
export interface MostReservedThemeIdListResponse {
|
||||
themeIds: string[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,14 +1,31 @@
|
||||
import {fetchMostReservedThemeIds} from '@_api/reservation/reservationAPIV2';
|
||||
import '@_css/home-page-v2.css';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import { mostReservedThemes } from '../../api/theme/themeAPI';
|
||||
import '../../css/home-page-v2.css';
|
||||
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<any[]>([]);
|
||||
const [ranking, setRanking] = useState<UserThemeRetrieveResponse[]>([]);
|
||||
const [selectedTheme, setSelectedTheme] = useState<UserThemeRetrieveResponse | null>(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 (
|
||||
<div className="home-container-v2">
|
||||
<h2 className="page-title">인기 테마</h2>
|
||||
<div className="theme-ranking-list-v2">
|
||||
{ranking.map(theme => (
|
||||
<div key={theme.id} className="theme-ranking-item-v2">
|
||||
<img className="thumbnail" src={theme.thumbnail} alt={theme.name} />
|
||||
<div key={theme.id} className="theme-ranking-item-v2" onClick={() => handleThemeClick(theme)}>
|
||||
<img className="thumbnail" src={theme.thumbnailUrl} alt={theme.name} />
|
||||
<div className="theme-info">
|
||||
<h5 className="theme-name">{theme.name}</h5>
|
||||
<p className="theme-description">{theme.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedTheme && (
|
||||
<div className="theme-modal-overlay" onClick={handleCloseModal}>
|
||||
<div className="theme-modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
<img className="modal-thumbnail" src={selectedTheme.thumbnailUrl} alt={selectedTheme.name} />
|
||||
<div className="modal-theme-info">
|
||||
<h2>{selectedTheme.name}</h2>
|
||||
<p>{selectedTheme.description}</p>
|
||||
<div className="theme-details">
|
||||
<p><strong>난이도:</strong> {selectedTheme.difficulty}</p>
|
||||
<p><strong>가격:</strong> {selectedTheme.price.toLocaleString()}원</p>
|
||||
<p><strong>예상 시간:</strong> {selectedTheme.expectedMinutesFrom} ~ {selectedTheme.expectedMinutesTo}분</p>
|
||||
<p><strong>이용 가능 인원:</strong> {selectedTheme.minParticipants} ~ {selectedTheme.maxParticipants}명</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-buttons">
|
||||
<button onClick={handleReservationClick} className="modal-button reserve">예약하기</button>
|
||||
<button onClick={handleCloseModal} className="modal-button close">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user