generated from pricelees/issue-pr-template
[#34] 회원 / 인증 도메인 재정의 #43
@ -1,5 +1,11 @@
|
|||||||
import apiClient from '../apiClient';
|
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> => {
|
export const createPendingReservation = async (request: PendingReservationCreateRequest): Promise<PendingReservationCreateResponse> => {
|
||||||
return await apiClient.post<PendingReservationCreateResponse>('/reservations/pending', request);
|
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> => {
|
export const fetchDetailById = async (reservationId: string): Promise<ReservationDetailRetrieveResponse> => {
|
||||||
return await apiClient.get<ReservationDetailRetrieveResponse>(`/reservations/${reservationId}/detail`);
|
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;
|
applicationDateTime: string;
|
||||||
payment: PaymentRetrieveResponse;
|
payment: PaymentRetrieveResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MostReservedThemeIdListResponse {
|
||||||
|
themeIds: string[];
|
||||||
|
}
|
||||||
@ -31,6 +31,7 @@
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
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;
|
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-ranking-item-v2:hover {
|
.theme-ranking-item-v2:hover {
|
||||||
@ -64,3 +65,101 @@
|
|||||||
color: #505a67;
|
color: #505a67;
|
||||||
margin: 0;
|
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 React, {useEffect, useState} from 'react';
|
||||||
import { mostReservedThemes } from '../../api/theme/themeAPI';
|
import {useNavigate} from 'react-router-dom';
|
||||||
import '../../css/home-page-v2.css';
|
import {findThemesByIds} from '../../api/theme/themeAPI';
|
||||||
|
import {type UserThemeRetrieveResponse} from '../../api/theme/themeTypes';
|
||||||
|
|
||||||
const HomePageV2: React.FC = () => {
|
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(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
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);
|
setRanking(response.themes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching ranking:', err);
|
console.error('Error fetching ranking:', err);
|
||||||
@ -18,20 +35,56 @@ const HomePageV2: React.FC = () => {
|
|||||||
fetchData();
|
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 (
|
return (
|
||||||
<div className="home-container-v2">
|
<div className="home-container-v2">
|
||||||
<h2 className="page-title">인기 테마</h2>
|
<h2 className="page-title">인기 테마</h2>
|
||||||
<div className="theme-ranking-list-v2">
|
<div className="theme-ranking-list-v2">
|
||||||
{ranking.map(theme => (
|
{ranking.map(theme => (
|
||||||
<div key={theme.id} className="theme-ranking-item-v2">
|
<div key={theme.id} className="theme-ranking-item-v2" onClick={() => handleThemeClick(theme)}>
|
||||||
<img className="thumbnail" src={theme.thumbnail} alt={theme.name} />
|
<img className="thumbnail" src={theme.thumbnailUrl} alt={theme.name} />
|
||||||
<div className="theme-info">
|
<div className="theme-info">
|
||||||
<h5 className="theme-name">{theme.name}</h5>
|
<h5 className="theme-name">{theme.name}</h5>
|
||||||
<p className="theme-description">{theme.description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user