roomescape-refactored/frontend/src/pages/ReservationStep2Page.tsx
pricelees 6bcec4c0ed [#44] 매장 기능 도입 (#45)
<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #44

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
- 매장 기능 도입 및 기존 기능에 적용
- 관리자 타입(본사, 매장, 전체) 분리 및 API별 적용

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
- 신규 기능 및 매장 기능 도입으로 수정된 기존 API 모두 통합 테스트 완료

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->
- 아직 미결제 예약 스케쥴링 작업 등 추가적인 작업이 필요하긴 하지만, 이 작업들은 배포 후 추가로 진행할 예정
- 다음 작업은 배포 + 초기 데이터 삽입

Reviewed-on: #45
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
2025-09-20 03:15:06 +00:00

139 lines
5.2 KiB
TypeScript

import { isLoginRequiredError } from '@_api/apiClient';
import { confirmPayment } from '@_api/payment/paymentAPI';
import { type PaymentConfirmRequest, PaymentType } from '@_api/payment/PaymentTypes';
import { confirmReservation } from '@_api/reservation/reservationAPI';
import '@_css/reservation-v2-1.css';
import React, { useEffect, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { formatDate } from 'src/util/DateTimeFormatter';
declare global {
interface Window {
PaymentWidget: any;
}
}
const ReservationStep2Page: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const paymentWidgetRef = useRef<any>(null);
const paymentMethodsRef = useRef<any>(null);
const { reservationId, storeName, themeName, themePrice, totalPrice, date, time, participantCount } = location.state || {};
const handleError = (err: any) => {
if (isLoginRequiredError(err)) {
alert('로그인이 필요해요.');
navigate('/login', { state: { from: location } });
} else {
const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.';
alert(message);
console.error(err);
}
};
useEffect(() => {
if (!reservationId) {
alert('잘못된 접근입니다.');
navigate('/reservation');
return;
}
const script = document.createElement('script');
script.src = 'https://js.tosspayments.com/v1/payment-widget';
script.async = true;
document.head.appendChild(script);
script.onload = () => {
const widgetClientKey = "test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm";
const paymentWidget = window.PaymentWidget(widgetClientKey, window.PaymentWidget.ANONYMOUS);
paymentWidgetRef.current = paymentWidget;
const paymentMethods = paymentWidget.renderPaymentMethods(
"#payment-method",
{ value: totalPrice, currency: "KRW" },
{ variantKey: "DEFAULT" }
);
paymentMethodsRef.current = paymentMethods;
};
}, [reservationId, totalPrice, navigate]);
const handlePayment = () => {
if (!paymentWidgetRef.current || !reservationId) {
alert('결제 위젯이 로드되지 않았거나 예약 정보가 없습니다.');
return;
}
const generateRandomString = () =>
crypto.randomUUID().replace(/-/g, '');
paymentWidgetRef.current.requestPayment({
orderId: generateRandomString(),
orderName: `${themeName} 예약 결제`,
amount: totalPrice,
}).then((data: any) => {
const paymentData: PaymentConfirmRequest = {
paymentKey: data.paymentKey,
orderId: data.orderId,
amount: totalPrice,
paymentType: data.paymentType || PaymentType.NORMAL,
};
confirmPayment(reservationId, paymentData)
.then(() => {
return confirmReservation(reservationId);
})
.then(() => {
alert('결제가 완료되었어요!');
navigate('/reservation/success', {
state: {
storeName: storeName,
themeName: themeName,
date: date,
time: time,
participantCount: participantCount,
totalPrice: totalPrice,
}
});
})
.catch(handleError);
}).catch((error: any) => {
console.error("Payment request error:", error);
alert("결제 요청 중 오류가 발생했습니다.");
});
};
if (!reservationId) {
return null;
}
return (
<div className="reservation-v21-container">
<h2 className="page-title"></h2>
<div className="step-section">
<h3> </h3>
<p><strong>:</strong> {formatDate(date)}</p>
<p><strong>:</strong> {time}</p>
<p><strong>:</strong> {themeName}</p>
<p><strong>:</strong> {storeName}</p>
<p><strong>:</strong> {participantCount}</p>
<p><strong>1 :</strong> {themePrice.toLocaleString()}</p>
<p><strong> :</strong> {totalPrice.toLocaleString()}</p>
</div>
<div className="step-section">
<h3> </h3>
<div id="payment-method" className="w-100"></div>
<div id="agreement" className="w-100"></div>
</div>
<div className="next-step-button-container">
<button onClick={handlePayment} className="next-step-button">
{totalPrice.toLocaleString()}
</button>
</div>
</div>
);
};
export default ReservationStep2Page;