From 675a5b8854367824add6a4bfd95c3ea75afa5bcd Mon Sep 17 00:00:00 2001 From: pricelees Date: Tue, 9 Sep 2025 00:43:39 +0000 Subject: [PATCH] =?UTF-8?q?[#41]=20=EC=98=88=EC=95=BD=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EC=9E=AC=EC=A0=95=EC=9D=98=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #41 ## ✨ 작업 내용 - 예약 스키마 & API 재정의 - 새로운 기능에 맞춘 프론트엔드 페이지 추가 - Controller 이후 응답(성공, 실패) 로그에 Endpoint 추가 - 전환으로 인해 미사용되는 코드 및 테스트 전체 제거 ## 🧪 테스트 테스트 커버리지 - 예약 & 결제 통합 테스트 작성 완료 - 결제 테스트는 통합 테스트에서는 Client를 mocking하는 방식 + 별도의 Client 슬라이스 테스트로 진행 ## 📚 참고 자료 및 기타 Reviewed-on: https://gitea.pricelees.me/pricelees/roomescape-refactored/pulls/42 Co-authored-by: pricelees Co-committed-by: pricelees --- frontend/src/App.tsx | 2 + frontend/src/api/member/memberTypes.ts | 6 + frontend/src/api/payment/PaymentTypes.ts | 73 ++ frontend/src/api/payment/paymentAPI.ts | 10 + .../src/api/reservation/reservationAPI.ts | 5 +- .../src/api/reservation/reservationAPIV2.ts | 23 + .../src/api/reservation/reservationTypes.ts | 82 +- .../src/api/reservation/reservationTypesV2.ts | 58 ++ frontend/src/api/schedule/scheduleAPI.ts | 4 + frontend/src/api/schedule/scheduleTypes.ts | 2 +- frontend/src/css/reservation-v2-1.css | 79 +- .../src/pages/admin/AdminSchedulePage.tsx | 2 +- .../src/pages/admin/AdminThemeEditPage.tsx | 2 +- frontend/src/pages/admin/ThemePage.tsx | 4 +- frontend/src/pages/v2/MyReservationPageV2.tsx | 65 +- frontend/src/pages/v2/ReservationFormPage.tsx | 121 +++ .../src/pages/v2/ReservationStep1PageV21.tsx | 58 +- .../src/pages/v2/ReservationStep2Page.tsx | 10 +- .../src/pages/v2/ReservationStep2PageV21.tsx | 63 +- .../pages/v2/ReservationSuccessPageV21.tsx | 15 +- .../roomescape/common/entity/BaseEntity.kt | 2 +- .../roomescape/common/entity/BaseEntityV2.kt | 48 +- .../exception/ExceptionControllerAdvice.kt | 22 +- .../common/log/ApiLogMessageConverter.kt | 4 + .../common/log/ControllerLoggingAspect.kt | 11 +- .../member/business/MemberService.kt | 12 +- .../persistence/MemberEntity.kt | 2 +- .../kotlin/roomescape/member/web/MemberDTO.kt | 15 + .../payment/business/PaymentService.kt | 150 ++-- .../PaymentWriter.kt} | 57 +- .../roomescape/payment/docs/PaymentAPI.kt | 35 + .../payment/implement/PaymentFinder.kt | 48 -- .../payment/implement/PaymentFinderV2.kt | 53 -- .../payment/implement/PaymentRequester.kt | 21 - .../payment/implement/PaymentWriter.kt | 81 -- .../PaymentCancelResponseDeserializer.kt | 29 - .../infrastructure/client/PaymentConfig.kt | 2 +- .../client/TossPaymentClient.kt | 108 --- .../infrastructure/client/TossPaymentDTO.kt | 22 - ...aymentCancelDTO.kt => TosspayCancelDTO.kt} | 17 +- .../infrastructure/client/TosspayClient.kt | 167 ++++ ...mentConfirmDTO.kt => TosspayConfirmDTO.kt} | 28 +- .../client/TosspayErrorResponse.kt | 6 + .../client/v2/TosspaymentClientV2.kt | 136 ---- .../infrastructure/common/PaymentTypes.kt | 18 +- .../persistence/CanceledPaymentEntity.kt | 37 +- .../persistence/CanceledPaymentRepository.kt | 2 +- .../{v2 => }/PaymentDetailEntity.kt | 6 +- .../persistence/PaymentDetailRepository.kt | 7 + .../persistence/PaymentEntity.kt | 46 +- .../persistence/PaymentRepository.kt | 4 - .../persistence/v2/CanceledPaymentEntityV2.kt | 24 - .../v2/CanceledPaymentRepositoryV2.kt | 7 - .../persistence/v2/PaymentDetailRepository.kt | 7 - .../persistence/v2/PaymentEntityV2.kt | 38 - .../persistence/v2/PaymentRepositoryV2.kt | 8 - .../payment/web/PaymentController.kt | 39 + .../roomescape/payment/web/PaymentDTO.kt | 152 +++- .../business/MyReservationFindService.kt | 54 -- .../business/ReservationFindService.kt | 56 -- .../business/ReservationService.kt | 168 ++++ .../business/ReservationValidator.kt | 38 + .../business/ReservationWithPaymentService.kt | 75 -- .../ReservationWithPaymentServiceV2.kt | 108 --- .../business/ReservationWriteService.kt | 104 --- .../reservation/docs/MyReservationAPI.kt | 33 - .../reservation/docs/ReservationAPI.kt | 146 +--- .../docs/ReservationWithPaymentAPI.kt | 61 -- .../exception/ReservationErrorCode.kt | 13 +- .../implement/ReservationFinder.kt | 113 --- .../implement/ReservationValidator.kt | 178 ----- .../implement/ReservationWriter.kt | 131 ---- .../persistence/CanceledReservationEntity.kt | 27 + .../CanceledReservationRepository.kt | 5 + .../persistence/ReservationEntity.kt | 71 +- .../persistence/ReservationRepository.kt | 71 +- .../ReservationSearchSpecification.kt | 80 -- .../web/MyReservationController.kt | 35 - .../reservation/web/MyReservationResponse.kt | 196 ----- .../reservation/web/ReservationController.kt | 187 +---- .../reservation/web/ReservationDto.kt | 70 ++ .../reservation/web/ReservationRequest.kt | 40 - .../reservation/web/ReservationResponse.kt | 93 --- .../web/ReservationWithPaymentController.kt | 60 -- .../web/ReservationWithPaymentDTO.kt | 57 -- .../schedule/business/ScheduleService.kt | 29 +- .../schedule/business/ScheduleValidator.kt | 2 +- .../roomescape/schedule/docs/ScheduleAPI.kt | 13 + .../schedule/exception/ScheduleErrorCode.kt | 1 + .../persistence/ScheduleEntity.kt | 12 +- .../persistence/ScheduleRepository.kt | 10 + .../schedule/web/ScheduleController.kt | 9 + .../roomescape/schedule/web/ScheduleDto.kt | 16 +- .../roomescape/theme/business/ThemeService.kt | 145 +++- .../theme/business/ThemeServiceV2.kt | 133 ---- ...{ThemeValidatorV2.kt => ThemeValidator.kt} | 10 +- .../kotlin/roomescape/theme/docs/ThemeAPI.kt | 52 -- .../theme/docs/{ThemeApiV2.kt => ThemeApi.kt} | 14 +- .../roomescape/theme/implement/ThemeFinder.kt | 47 -- .../theme/implement/ThemeValidator.kt | 43 -- .../roomescape/theme/implement/ThemeWriter.kt | 41 - .../infrastructure/persistence/ThemeEntity.kt | 68 +- .../persistence/ThemeRepository.kt | 26 +- .../persistence/v2/ThemeEntityV2.kt | 66 -- .../persistence/v2/ThemeRepositoryV2.kt | 12 - .../roomescape/theme/web/ThemeController.kt | 66 +- .../roomescape/theme/web/ThemeControllerV2.kt | 69 -- .../kotlin/roomescape/theme/web/ThemeDTO.kt | 60 -- .../theme/web/{ThemeDtoV2.kt => ThemeDto.kt} | 28 +- .../roomescape/time/business/TimeService.kt | 74 -- .../business/domain/TimeWithAvailability.kt | 12 - .../kotlin/roomescape/time/docs/TimeAPI.kt | 50 -- .../time/exception/TimeErrorCode.kt | 14 - .../time/exception/TimeException.kt | 9 - .../roomescape/time/implement/TimeFinder.kt | 60 -- .../time/implement/TimeValidator.kt | 41 - .../roomescape/time/implement/TimeWriter.kt | 37 - .../infrastructure/persistence/TimeEntity.kt | 21 - .../persistence/TimeRepository.kt | 8 - .../roomescape/time/web/TimeController.kt | 51 -- .../kotlin/roomescape/time/web/TimeDTO.kt | 55 -- src/main/resources/schema/schema-h2.sql | 90 +-- src/main/resources/schema/schema-mysql.sql | 92 +-- .../auth/business/AuthServiceTest.kt | 82 -- .../auth/infrastructure/jwt/JwtHandlerTest.kt | 59 -- .../roomescape/auth/web/AuthControllerTest.kt | 151 ---- .../auth/web/support/CookieUtilsTest.kt | 26 - .../common/config/JacksonConfigTest.kt | 6 +- .../common/log/ApiLogMessageConverterTest.kt | 11 +- .../member/business/MemberServiceTest.kt | 100 --- .../member/controller/MemberControllerTest.kt | 136 ---- .../member/implement/MemberFinderTest.kt | 85 -- .../member/implement/MemberValidatorTest.kt | 44 -- .../member/implement/MemberWriterTest.kt | 65 -- .../persistence/MemberRepositoryTest.kt | 63 -- .../roomescape/payment/PaymentAPITest.kt | 361 +++++++++ .../payment/SampleTosspayConstant.kt | 288 +++++++ ...mentClientTest.kt => TosspayClientTest.kt} | 103 ++- .../payment/business/PaymentServiceTest.kt | 166 ---- .../payment/implement/PaymentFinderTest.kt | 93 --- .../payment/implement/PaymentWriterTest.kt | 121 --- .../PaymentCancelResponseDeserializerTest.kt | 34 - .../client/SampleTossPaymentConst.kt | 194 ----- .../CanceledPaymentRepositoryTest.kt | 39 - .../persistence/PaymentRepositoryTest.kt | 56 -- .../reservation/ReservationApiTest.kt | 571 ++++++++++++++ .../business/ReservationCommandServiceTest.kt | 284 ------- .../business/ReservationQueryServiceTest.kt | 118 --- .../ReservationWithPaymentServiceTest.kt | 122 --- .../implement/ReservationFinderTest.kt | 64 -- .../implement/ReservationValidatorTest.kt | 170 ---- .../implement/ReservationWriterTest.kt | 246 ------ .../persistence/ReservationRepositoryTest.kt | 205 ----- .../ReservationSearchSpecificationTest.kt | 195 ----- .../web/ReservationControllerTest.kt | 730 ------------------ .../roomescape/schedule/ScheduleApiTest.kt | 129 +++- .../kotlin/roomescape/theme/ThemeApiTest.kt | 152 ++-- .../theme/business/ThemeServiceTest.kt | 162 ---- .../theme/implement/ThemeFinderTest.kt | 76 -- .../theme/implement/ThemeValidatorTest.kt | 79 -- .../theme/implement/ThemeWriterTest.kt | 86 --- .../persistence/ThemeRepositoryTest.kt | 144 ---- .../theme/util/TestThemeDataHelper.kt | 57 -- .../theme/web/MostReservedThemeApiTest.kt | 110 --- .../theme/web/ThemeControllerTest.kt | 278 ------- .../time/business/TimeServiceTest.kt | 180 ----- .../time/implement/TimeFinderTest.kt | 144 ---- .../time/implement/TimeValidatorTest.kt | 74 -- .../time/implement/TimeWriterTest.kt | 84 -- .../persistence/TimeRepositoryTest.kt | 33 - .../roomescape/time/web/TimeControllerTest.kt | 312 -------- .../roomescape/util/DummyInitializer.kt | 224 ++++++ src/test/kotlin/roomescape/util/Fixtures.kt | 293 +++---- src/test/kotlin/roomescape/util/FixturesV2.kt | 51 -- .../kotlin/roomescape/util/KotestConfig.kt | 60 +- .../roomescape/util/RestAssuredUtils.kt | 11 +- .../roomescape/util/RoomescapeApiTest.kt | 145 ---- 177 files changed, 3781 insertions(+), 10102 deletions(-) create mode 100644 frontend/src/api/payment/PaymentTypes.ts create mode 100644 frontend/src/api/payment/paymentAPI.ts create mode 100644 frontend/src/api/reservation/reservationTypesV2.ts create mode 100644 frontend/src/pages/v2/ReservationFormPage.tsx rename src/main/kotlin/roomescape/payment/{implement/PaymentWriterV2.kt => business/PaymentWriter.kt} (51%) create mode 100644 src/main/kotlin/roomescape/payment/docs/PaymentAPI.kt delete mode 100644 src/main/kotlin/roomescape/payment/implement/PaymentFinder.kt delete mode 100644 src/main/kotlin/roomescape/payment/implement/PaymentFinderV2.kt delete mode 100644 src/main/kotlin/roomescape/payment/implement/PaymentRequester.kt delete mode 100644 src/main/kotlin/roomescape/payment/implement/PaymentWriter.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentDTO.kt rename src/main/kotlin/roomescape/payment/infrastructure/client/{v2/TosspaymentCancelDTO.kt => TosspayCancelDTO.kt} (82%) create mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/TosspayClient.kt rename src/main/kotlin/roomescape/payment/infrastructure/client/{v2/TosspaymentConfirmDTO.kt => TosspayConfirmDTO.kt} (79%) create mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/TosspayErrorResponse.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/v2/TosspaymentClientV2.kt rename src/main/kotlin/roomescape/payment/infrastructure/persistence/{v2 => }/PaymentDetailEntity.kt (92%) create mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentDetailRepository.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/v2/CanceledPaymentEntityV2.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/v2/CanceledPaymentRepositoryV2.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/v2/PaymentDetailRepository.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/v2/PaymentEntityV2.kt delete mode 100644 src/main/kotlin/roomescape/payment/infrastructure/persistence/v2/PaymentRepositoryV2.kt create mode 100644 src/main/kotlin/roomescape/payment/web/PaymentController.kt delete mode 100644 src/main/kotlin/roomescape/reservation/business/MyReservationFindService.kt delete mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationFindService.kt create mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationService.kt create mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationValidator.kt delete mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationWithPaymentService.kt delete mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationWithPaymentServiceV2.kt delete mode 100644 src/main/kotlin/roomescape/reservation/business/ReservationWriteService.kt delete mode 100644 src/main/kotlin/roomescape/reservation/docs/MyReservationAPI.kt delete mode 100644 src/main/kotlin/roomescape/reservation/docs/ReservationWithPaymentAPI.kt delete mode 100644 src/main/kotlin/roomescape/reservation/implement/ReservationFinder.kt delete mode 100644 src/main/kotlin/roomescape/reservation/implement/ReservationValidator.kt delete mode 100644 src/main/kotlin/roomescape/reservation/implement/ReservationWriter.kt create mode 100644 src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt create mode 100644 src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationRepository.kt delete mode 100644 src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/MyReservationController.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/MyReservationResponse.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationDto.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationRequest.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentController.kt delete mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentDTO.kt delete mode 100644 src/main/kotlin/roomescape/theme/business/ThemeServiceV2.kt rename src/main/kotlin/roomescape/theme/business/{ThemeValidatorV2.kt => ThemeValidator.kt} (94%) delete mode 100644 src/main/kotlin/roomescape/theme/docs/ThemeAPI.kt rename src/main/kotlin/roomescape/theme/docs/{ThemeApiV2.kt => ThemeApi.kt} (83%) delete mode 100644 src/main/kotlin/roomescape/theme/implement/ThemeFinder.kt delete mode 100644 src/main/kotlin/roomescape/theme/implement/ThemeValidator.kt delete mode 100644 src/main/kotlin/roomescape/theme/implement/ThemeWriter.kt delete mode 100644 src/main/kotlin/roomescape/theme/infrastructure/persistence/v2/ThemeEntityV2.kt delete mode 100644 src/main/kotlin/roomescape/theme/infrastructure/persistence/v2/ThemeRepositoryV2.kt delete mode 100644 src/main/kotlin/roomescape/theme/web/ThemeControllerV2.kt delete mode 100644 src/main/kotlin/roomescape/theme/web/ThemeDTO.kt rename src/main/kotlin/roomescape/theme/web/{ThemeDtoV2.kt => ThemeDto.kt} (83%) delete mode 100644 src/main/kotlin/roomescape/time/business/TimeService.kt delete mode 100644 src/main/kotlin/roomescape/time/business/domain/TimeWithAvailability.kt delete mode 100644 src/main/kotlin/roomescape/time/docs/TimeAPI.kt delete mode 100644 src/main/kotlin/roomescape/time/exception/TimeErrorCode.kt delete mode 100644 src/main/kotlin/roomescape/time/exception/TimeException.kt delete mode 100644 src/main/kotlin/roomescape/time/implement/TimeFinder.kt delete mode 100644 src/main/kotlin/roomescape/time/implement/TimeValidator.kt delete mode 100644 src/main/kotlin/roomescape/time/implement/TimeWriter.kt delete mode 100644 src/main/kotlin/roomescape/time/infrastructure/persistence/TimeEntity.kt delete mode 100644 src/main/kotlin/roomescape/time/infrastructure/persistence/TimeRepository.kt delete mode 100644 src/main/kotlin/roomescape/time/web/TimeController.kt delete mode 100644 src/main/kotlin/roomescape/time/web/TimeDTO.kt delete mode 100644 src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt delete mode 100644 src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt delete mode 100644 src/test/kotlin/roomescape/auth/web/support/CookieUtilsTest.kt delete mode 100644 src/test/kotlin/roomescape/member/business/MemberServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt delete mode 100644 src/test/kotlin/roomescape/member/implement/MemberFinderTest.kt delete mode 100644 src/test/kotlin/roomescape/member/implement/MemberValidatorTest.kt delete mode 100644 src/test/kotlin/roomescape/member/implement/MemberWriterTest.kt delete mode 100644 src/test/kotlin/roomescape/member/infrastructure/persistence/MemberRepositoryTest.kt create mode 100644 src/test/kotlin/roomescape/payment/PaymentAPITest.kt create mode 100644 src/test/kotlin/roomescape/payment/SampleTosspayConstant.kt rename src/test/kotlin/roomescape/payment/{infrastructure/client/TossPaymentClientTest.kt => TosspayClientTest.kt} (56%) delete mode 100644 src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/payment/implement/PaymentFinderTest.kt delete mode 100644 src/test/kotlin/roomescape/payment/implement/PaymentWriterTest.kt delete mode 100644 src/test/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializerTest.kt delete mode 100644 src/test/kotlin/roomescape/payment/infrastructure/client/SampleTossPaymentConst.kt delete mode 100644 src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt delete mode 100644 src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt create mode 100644 src/test/kotlin/roomescape/reservation/ReservationApiTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/business/ReservationCommandServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/business/ReservationQueryServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/implement/ReservationFinderTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/implement/ReservationValidatorTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/implement/ReservationWriterTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt delete mode 100644 src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/implement/ThemeFinderTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/implement/ThemeValidatorTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/implement/ThemeWriterTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/util/TestThemeDataHelper.kt delete mode 100644 src/test/kotlin/roomescape/theme/web/MostReservedThemeApiTest.kt delete mode 100644 src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt delete mode 100644 src/test/kotlin/roomescape/time/business/TimeServiceTest.kt delete mode 100644 src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt delete mode 100644 src/test/kotlin/roomescape/time/implement/TimeValidatorTest.kt delete mode 100644 src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt delete mode 100644 src/test/kotlin/roomescape/time/infrastructure/persistence/TimeRepositoryTest.kt delete mode 100644 src/test/kotlin/roomescape/time/web/TimeControllerTest.kt create mode 100644 src/test/kotlin/roomescape/util/DummyInitializer.kt delete mode 100644 src/test/kotlin/roomescape/util/FixturesV2.kt delete mode 100644 src/test/kotlin/roomescape/util/RoomescapeApiTest.kt diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e85af96a..b95ec603 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,6 +23,7 @@ import ReservationSuccessPageV21 from './pages/v2/ReservationSuccessPageV21'; import HomePageV2 from './pages/v2/HomePageV2'; import LoginPageV2 from './pages/v2/LoginPageV2'; import SignupPageV2 from './pages/v2/SignupPageV2'; +import ReservationFormPage from './pages/v2/ReservationFormPage'; import AdminThemeEditPage from './pages/admin/AdminThemeEditPage'; import AdminSchedulePage from './pages/admin/AdminSchedulePage'; @@ -72,6 +73,7 @@ function App() { {/* V2.1 Reservation Flow */} } /> + } /> } /> } /> diff --git a/frontend/src/api/member/memberTypes.ts b/frontend/src/api/member/memberTypes.ts index 32d4ab17..6dc36555 100644 --- a/frontend/src/api/member/memberTypes.ts +++ b/frontend/src/api/member/memberTypes.ts @@ -17,3 +17,9 @@ export interface SignupResponse { id: string; name: string; } + +export interface MemberSummaryRetrieveResponse { + id: string; + name: string; + email: string; +} diff --git a/frontend/src/api/payment/PaymentTypes.ts b/frontend/src/api/payment/PaymentTypes.ts new file mode 100644 index 00000000..93b015df --- /dev/null +++ b/frontend/src/api/payment/PaymentTypes.ts @@ -0,0 +1,73 @@ +export interface PaymentConfirmRequest { + paymentKey: string; + orderId: string; + amount: number; + paymentType: PaymentType; +} + +export interface PaymentCancelRequest { + reservationId: string, + cancelReason: String +} + +// V2 types +export const PaymentType = { + NORMAL: 'NORMAL', + BILLING: 'BILLING', + BRANDPAY: 'BRANDPAY' +} as const; + +export type PaymentType = + | typeof PaymentType.NORMAL + | typeof PaymentType.BILLING + | typeof PaymentType.BRANDPAY; + +export interface PaymentCreateResponseV2 { + paymentId: string; + detailId: string; +} + +export interface PaymentRetrieveResponse { + orderId: string; + totalAmount: number; + method: string; + status: 'DONE' | 'CANCELED'; + requestedAt: string; + approvedAt: string; + detail: CardPaymentDetail | BankTransferPaymentDetail | EasyPayPrepaidPaymentDetail; + cancellation?: CanceledPaymentDetailResponse; +} + +export interface CardPaymentDetail { + type: 'CARD'; + issuerCode: string; + cardType: 'CREDIT' | 'CHECK' | 'GIFT'; + ownerType: 'PERSONAL' | 'CORPORATE'; + cardNumber: string; + amount: number; + approvalNumber: string; + installmentPlanMonths: number; + isInterestFree: boolean; + easypayProviderName?: string; + easypayDiscountAmount?: number; +} + +export interface BankTransferPaymentDetail { + type: 'BANK_TRANSFER'; + bankName: string; + settlementStatus: string; +} + +export interface EasyPayPrepaidPaymentDetail { + type: 'EASYPAY_PREPAID'; + providerName: string; + amount: number; + discountAmount: number; +} + +export interface CanceledPaymentDetailResponse { + cancellationRequestedAt: string; // ISO 8601 format + cancellationApprovedAt: string; // ISO 8601 format + cancelReason: string; + canceledBy: string; +} diff --git a/frontend/src/api/payment/paymentAPI.ts b/frontend/src/api/payment/paymentAPI.ts new file mode 100644 index 00000000..c5481ec8 --- /dev/null +++ b/frontend/src/api/payment/paymentAPI.ts @@ -0,0 +1,10 @@ +import apiClient from "@_api/apiClient"; +import type { PaymentCancelRequest, PaymentConfirmRequest, PaymentCreateResponseV2 } from "./PaymentTypes"; + +export const confirmPayment = async (reservationId: string, request: PaymentConfirmRequest): Promise => { + return await apiClient.post(`/payments?reservationId=${reservationId}`, request); +}; + +export const cancelPayment = async (request: PaymentCancelRequest): Promise => { + return await apiClient.post(`/payments/cancel`, request); +}; diff --git a/frontend/src/api/reservation/reservationAPI.ts b/frontend/src/api/reservation/reservationAPI.ts index 01a39c4a..ca370e74 100644 --- a/frontend/src/api/reservation/reservationAPI.ts +++ b/frontend/src/api/reservation/reservationAPI.ts @@ -85,10 +85,7 @@ export const confirmReservationPayment = async (id: string, data: ReservationPay return await apiClient.post(`/v2/reservations/${id}/pay`, data, true); }; -// POST /v2/reservations/{id}/cancel -export const cancelReservationV2 = async (id: string, cancelReason: string): Promise => { - return await apiClient.post(`/v2/reservations/${id}/cancel`, { cancelReason }, true); -}; + // GET /v2/reservations export const fetchMyReservationsV2 = async (): Promise => { diff --git a/frontend/src/api/reservation/reservationAPIV2.ts b/frontend/src/api/reservation/reservationAPIV2.ts index e69de29b..01992108 100644 --- a/frontend/src/api/reservation/reservationAPIV2.ts +++ b/frontend/src/api/reservation/reservationAPIV2.ts @@ -0,0 +1,23 @@ +import apiClient from '../apiClient'; +import type { PendingReservationCreateRequest, PendingReservationCreateResponse, ReservationDetailRetrieveResponse, ReservationSummaryRetrieveListResponse } from './reservationTypesV2'; + +export const createPendingReservation = async (request: PendingReservationCreateRequest): Promise => { + return await apiClient.post('/reservations/pending', request); +}; + +export const confirmReservation = async (reservationId: string): Promise => { + await apiClient.post(`/reservations/${reservationId}/confirm`, {}); +}; + + +export const cancelReservation = async (id: string, cancelReason: string): Promise => { + return await apiClient.post(`/reservations/${id}/cancel`, { cancelReason }, true); +}; + +export const fetchSummaryByMember = async (): Promise => { + return await apiClient.get('/reservations/summary'); +} + +export const fetchDetailById = async (reservationId: string): Promise => { + return await apiClient.get(`/reservations/${reservationId}/detail`); +} diff --git a/frontend/src/api/reservation/reservationTypes.ts b/frontend/src/api/reservation/reservationTypes.ts index 5501ed8c..29d20a57 100644 --- a/frontend/src/api/reservation/reservationTypes.ts +++ b/frontend/src/api/reservation/reservationTypes.ts @@ -1,4 +1,5 @@ -import type { MemberRetrieveResponse } from '@_api/member/memberTypes'; +import type { MemberRetrieveResponse, MemberSummaryRetrieveResponse } from '@_api/member/memberTypes'; +import type { PaymentRetrieveResponse, PaymentType } from '@_api/payment/PaymentTypes'; import type { ThemeRetrieveResponse } from '@_api/theme/themeTypes'; import type { TimeRetrieveResponse } from '@_api/time/timeTypes'; @@ -77,18 +78,6 @@ export interface ReservationSearchQuery { dateTo?: string; } -// V2 types -export const PaymentType = { - NORMAL: 'NORMAL', - BILLING: 'BILLING', - BRANDPAY: 'BRANDPAY' -} as const; - -export type PaymentType = - | typeof PaymentType.NORMAL - | typeof PaymentType.BILLING - | typeof PaymentType.BRANDPAY; - export const PaymentStatus = { IN_PROGRESS: '결제 진행 중', DONE: '결제 완료', @@ -123,7 +112,7 @@ export interface ReservationPaymentRequest { paymentKey: string; orderId: string; amount: number; - paymentType: PaymentType + paymentType: PaymentType; } export interface ReservationPaymentResponse { @@ -133,75 +122,14 @@ export interface ReservationPaymentResponse { paymentStatus: PaymentStatus; } -export interface ReservationSummaryV2 { - id: string; - themeName: string; - date: string; - startAt: string; - status: string; // 'CONFIRMED', 'CANCELED_BY_USER', etc. -} -export interface ReservationSummaryListV2 { - reservations: ReservationSummaryV2[]; -} export interface ReservationDetailV2 { id: string; - user: UserDetailV2; + user: MemberSummaryRetrieveResponse; themeName: string; date: string; startAt: string; applicationDateTime: string; - payment: PaymentV2; - cancellation: CancellationV2 | null; -} - -export interface UserDetailV2 { - id: string; - name: string; - email: string; -} - -export interface PaymentV2 { - orderId: string; - totalAmount: number; - method: string; - status: 'DONE' | 'CANCELED'; - requestedAt: string; - approvedAt: string; - detail: CardPaymentDetailV2 | BankTransferPaymentDetailV2 | EasyPayPrepaidPaymentDetailV2; -} - -export interface CardPaymentDetailV2 { - type: 'CARD'; - issuerCode: string; - cardType: 'CREDIT' | 'CHECK' | 'GIFT'; - ownerType: 'PERSONAL' | 'CORPORATE'; - cardNumber: string; - amount: number; - approvalNumber: string; - installmentPlanMonths: number; - isInterestFree: boolean; - easypayProviderName?: string; - easypayDiscountAmount?: number; -} - -export interface BankTransferPaymentDetailV2 { - type: 'BANK_TRANSFER'; - bankName: string; - settlementStatus: string; -} - -export interface EasyPayPrepaidPaymentDetailV2 { - type: 'EASYPAY_PREPAID'; - providerName: string; - amount: number; - discountAmount: number; -} - -export interface CancellationV2 { - cancellationRequestedAt: string; // ISO 8601 format - cancellationApprovedAt: string; // ISO 8601 format - cancelReason: string; - canceledBy: string; + payment: PaymentRetrieveResponse; } diff --git a/frontend/src/api/reservation/reservationTypesV2.ts b/frontend/src/api/reservation/reservationTypesV2.ts new file mode 100644 index 00000000..3dc595fa --- /dev/null +++ b/frontend/src/api/reservation/reservationTypesV2.ts @@ -0,0 +1,58 @@ +import type { MemberSummaryRetrieveResponse } from "@_api/member/memberTypes"; +import type { PaymentRetrieveResponse } from "@_api/payment/PaymentTypes"; + +export const ReservationStatusV2 = { + PENDING: 'PENDING', + CONFIRMED: 'CONFIRMED', + CANCELED: 'CANCELED', + FAILED: 'FAILED', + EXPIRED: 'EXPIRED' +} as const; + +export type ReservationStatusV2 = + | typeof ReservationStatusV2.PENDING + | typeof ReservationStatusV2.CONFIRMED + | typeof ReservationStatusV2.CANCELED + | typeof ReservationStatusV2.FAILED + | typeof ReservationStatusV2.EXPIRED; + +export interface PendingReservationCreateRequest { + scheduleId: string, + reserverName: string, + reserverContact: string, + participantCount: number, + requirement: string +} + +export interface PendingReservationCreateResponse { + id: string +} + +export interface ReservationSummaryRetrieveResponse { + id: string; + themeName: string; + date: string; + startAt: string; + status: ReservationStatusV2; +} + +export interface ReservationSummaryRetrieveListResponse { + reservations: ReservationSummaryRetrieveResponse[]; +} + +export interface ReservationDetailRetrieveResponse { + id: string; + member: MemberSummaryRetrieveResponse; + applicationDateTime: string; + payment: PaymentRetrieveResponse; +} + +export interface ReservationDetail { + id: string; + themeName: string; + date: string; + startAt: string; + member: MemberSummaryRetrieveResponse; + applicationDateTime: string; + payment: PaymentRetrieveResponse; +} diff --git a/frontend/src/api/schedule/scheduleAPI.ts b/frontend/src/api/schedule/scheduleAPI.ts index 403a5204..f828fa2b 100644 --- a/frontend/src/api/schedule/scheduleAPI.ts +++ b/frontend/src/api/schedule/scheduleAPI.ts @@ -30,3 +30,7 @@ export const updateSchedule = async (id: string, request: ScheduleUpdateRequest) export const deleteSchedule = async (id: string): Promise => { await apiClient.del(`/schedules/${id}`); }; + +export const holdSchedule = async (id: string): Promise => { + await apiClient.patch(`/schedules/${id}/hold`, {}); +}; diff --git a/frontend/src/api/schedule/scheduleTypes.ts b/frontend/src/api/schedule/scheduleTypes.ts index 73550b46..6230cf01 100644 --- a/frontend/src/api/schedule/scheduleTypes.ts +++ b/frontend/src/api/schedule/scheduleTypes.ts @@ -1,6 +1,6 @@ export enum ScheduleStatus { AVAILABLE = 'AVAILABLE', - PENDING = 'PENDING', + HOLD = 'HOLD', RESERVED = 'RESERVED', BLOCKED = 'BLOCKED', } diff --git a/frontend/src/css/reservation-v2-1.css b/frontend/src/css/reservation-v2-1.css index 6e56c6ba..76820556 100644 --- a/frontend/src/css/reservation-v2-1.css +++ b/frontend/src/css/reservation-v2-1.css @@ -417,4 +417,81 @@ } .modal-actions .confirm-button:hover { background-color: #1B64DA; -} \ No newline at end of file +} + +/* Styles for ReservationFormPage */ +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + font-weight: bold; + margin-bottom: 8px; + color: #333; +} + +.form-group input[type="text"], +.form-group input[type="tel"], +.form-group input[type="number"], +.form-group textarea { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 8px; + font-size: 16px; + box-sizing: border-box; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.form-group input:focus, .form-group textarea:focus { + outline: none; + border-color: #3182F6; + box-shadow: 0 0 0 3px rgba(49, 130, 246, 0.2); +} + +.form-group textarea { + resize: vertical; + min-height: 100px; +} + +.participant-control { + display: flex; + align-items: center; +} + +.participant-control input { + text-align: center; + border-left: none; + border-right: none; + width: 60px; + border-radius: 0; +} + +.participant-control button { + width: 44px; + height: 44px; + border: 1px solid #ccc; + background-color: #f0f0f0; + font-size: 20px; + cursor: pointer; + transition: background-color 0.2s; +} + +.participant-control button:hover:not(:disabled) { + background-color: #e0e0e0; +} + +.participant-control button:disabled { + background-color: #e9ecef; + cursor: not-allowed; + color: #aaa; +} + +.participant-control button:first-of-type { + border-radius: 8px 0 0 8px; +} + +.participant-control button:last-of-type { + border-radius: 0 8px 8px 0; +} diff --git a/frontend/src/pages/admin/AdminSchedulePage.tsx b/frontend/src/pages/admin/AdminSchedulePage.tsx index 28657fe6..7f8c5793 100644 --- a/frontend/src/pages/admin/AdminSchedulePage.tsx +++ b/frontend/src/pages/admin/AdminSchedulePage.tsx @@ -11,7 +11,7 @@ const getScheduleStatusText = (status: ScheduleStatus): string => { switch (status) { case ScheduleStatus.AVAILABLE: return '예약 가능'; - case ScheduleStatus.PENDING: + case ScheduleStatus.HOLD: return '예약 진행 중'; case ScheduleStatus.RESERVED: return '예약 완료'; diff --git a/frontend/src/pages/admin/AdminThemeEditPage.tsx b/frontend/src/pages/admin/AdminThemeEditPage.tsx index 88a8fc51..0121d5dd 100644 --- a/frontend/src/pages/admin/AdminThemeEditPage.tsx +++ b/frontend/src/pages/admin/AdminThemeEditPage.tsx @@ -193,7 +193,7 @@ const AdminThemeEditPage: React.FC = () => {
- +
diff --git a/frontend/src/pages/admin/ThemePage.tsx b/frontend/src/pages/admin/ThemePage.tsx index f2daaba4..68eec8b0 100644 --- a/frontend/src/pages/admin/ThemePage.tsx +++ b/frontend/src/pages/admin/ThemePage.tsx @@ -37,7 +37,7 @@ const AdminThemePage: React.FC = () => { navigate('/admin/theme/edit/new'); }; - const handleManageClick = (themeId: number) => { + const handleManageClick = (themeId: string) => { navigate(`/admin/theme/edit/${themeId}`); }; @@ -54,7 +54,7 @@ const AdminThemePage: React.FC = () => { 이름 난이도 - 가격 + 1인당 요금 공개여부 diff --git a/frontend/src/pages/v2/MyReservationPageV2.tsx b/frontend/src/pages/v2/MyReservationPageV2.tsx index ccbcb99e..a229b953 100644 --- a/frontend/src/pages/v2/MyReservationPageV2.tsx +++ b/frontend/src/pages/v2/MyReservationPageV2.tsx @@ -1,10 +1,8 @@ +import { cancelPayment } from '@_api/payment/paymentAPI'; +import type { PaymentRetrieveResponse } from '@_api/payment/PaymentTypes'; +import { cancelReservation, fetchDetailById, fetchSummaryByMember } from '@_api/reservation/reservationAPIV2'; +import type { ReservationDetail, ReservationSummaryRetrieveResponse } from '@_api/reservation/reservationTypesV2'; import React, { useEffect, useState } from 'react'; -import { - cancelReservationV2, - fetchMyReservationsV2, - fetchReservationDetailV2 -} from '../../api/reservation/reservationAPI'; -import type { PaymentV2, ReservationDetailV2, ReservationSummaryV2 } from '../../api/reservation/reservationTypes'; import '../../css/my-reservation-v2.css'; const formatDisplayDateTime = (dateTime: any): string => { @@ -78,7 +76,7 @@ const formatCardDateTime = (dateStr: string, timeStr: string): string => { // --- Cancellation View Component --- const CancellationView: React.FC<{ - reservation: ReservationDetailV2; + reservation: ReservationDetail; onCancelSubmit: (reason: string) => void; onBack: () => void; isCancelling: boolean; @@ -119,13 +117,12 @@ const CancellationView: React.FC<{ }; -// --- Reservation Detail View Component --- const ReservationDetailView: React.FC<{ - reservation: ReservationDetailV2; + reservation: ReservationDetail; onGoToCancel: () => void; }> = ({ reservation, onGoToCancel }) => { - const renderPaymentDetails = (payment: PaymentV2) => { + const renderPaymentDetails = (payment: PaymentRetrieveResponse) => { const { detail } = payment; switch (detail.type) { @@ -178,8 +175,8 @@ const ReservationDetailView: React.FC<{

예약 정보

예약 테마: {reservation.themeName}

이용 예정일: {formatCardDateTime(reservation.date, reservation.startAt)}

-

예약자 이름: {reservation.user.name}

-

예약자 이메일: {reservation.user.email}

+

예약자 이름: {reservation.member.name}

+

예약자 이메일: {reservation.member.email}

예약 신청 일시: {formatDisplayDateTime(reservation.applicationDateTime)}

@@ -188,13 +185,13 @@ const ReservationDetailView: React.FC<{ {renderPaymentDetails(reservation.payment)}

결제 승인 일시: {formatDisplayDateTime(reservation.payment.approvedAt)}

- {reservation.cancellation && ( + {reservation.payment.cancellation && (

취소 정보

-

취소 요청 일시: {formatDisplayDateTime(reservation.cancellation.cancellationRequestedAt)}

-

환불 완료 일시: {formatDisplayDateTime(reservation.cancellation.cancellationApprovedAt)}

-

취소 사유: {reservation.cancellation.cancelReason}

-

취소 요청자: {reservation.cancellation.canceledBy == reservation.user.id ? '회원 본인' : '관리자'}

+

취소 요청 일시: {formatDisplayDateTime(reservation.payment.cancellation.cancellationRequestedAt)}

+

환불 완료 일시: {formatDisplayDateTime(reservation.payment.cancellation.cancellationApprovedAt)}

+

취소 사유: {reservation.payment.cancellation.cancelReason}

+

취소 요청자: {reservation.payment.cancellation.canceledBy == reservation.member.id ? '회원 본인' : '관리자'}

)} {reservation.payment.status !== 'CANCELED' && ( @@ -208,11 +205,11 @@ const ReservationDetailView: React.FC<{ // --- Main Page Component --- const MyReservationPageV2: React.FC = () => { - const [reservations, setReservations] = useState([]); + const [reservations, setReservations] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [selectedReservation, setSelectedReservation] = useState(null); + const [selectedReservation, setSelectedReservation] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [isDetailLoading, setIsDetailLoading] = useState(false); const [detailError, setDetailError] = useState(null); @@ -223,7 +220,7 @@ const MyReservationPageV2: React.FC = () => { const loadReservations = async () => { try { setIsLoading(true); - const data = await fetchMyReservationsV2(); + const data = await fetchSummaryByMember(); setReservations(data.reservations); setError(null); } catch (err) { @@ -237,14 +234,21 @@ const MyReservationPageV2: React.FC = () => { loadReservations(); }, []); - const handleShowDetail = async (id: string) => { + const handleShowDetail = async (id: string, themeName: string, date: string, time: string) => { try { setIsDetailLoading(true); setDetailError(null); setModalView('detail'); - const detailData = await fetchReservationDetailV2(id); - console.log('상세 정보:', detailData); - setSelectedReservation(detailData); + const detailData = await fetchDetailById(id); + setSelectedReservation({ + id: detailData.id, + themeName: themeName, + date: date, + startAt: time, + member: detailData.member, + applicationDateTime: detailData.applicationDateTime, + payment: detailData.payment + }); setIsModalOpen(true); } catch (err) { setDetailError('예약 상세 정보를 불러오는 데 실패했습니다.'); @@ -268,16 +272,18 @@ const MyReservationPageV2: React.FC = () => { try { setIsCancelling(true); setDetailError(null); - await cancelReservationV2(selectedReservation.id, reason); + await cancelReservation(selectedReservation.id, reason); + cancelPayment({ reservationId: selectedReservation.id, cancelReason: reason }); alert('예약을 취소했어요. 결제 취소까지는 3-5일 정도 소요될 수 있어요.'); handleCloseModal(); - loadReservations(); // Refresh the list + await loadReservations(); // Refresh the list } catch (err) { setDetailError(err instanceof Error ? err.message : '예약 취소에 실패했습니다.'); } finally { - setIsCancelling(false); + setIsCancelling(true); } }; + console.log("reservations=", reservations); return (
@@ -289,14 +295,13 @@ const MyReservationPageV2: React.FC = () => { {!isLoading && !error && (
{reservations.map((res) => ( - console.log(res), -
+

{res.themeName}

{formatCardDateTime(res.date, res.startAt)}

+
+ ); + } + + return ( +
+

예약 정보 입력

+ +
+

예약 내용 확인

+

테마: {theme.name}

+

날짜: {formatDate(date)}

+

시간: {formatTime(time)}

+
+ +
+

예약자 정보

+
+ + setReserverName(e.target.value)} /> +
+
+ + setReserverContact(e.target.value)} placeholder="'-' 없이 입력"/> +
+
+ +
+ setParticipantCount(Math.max(theme.minParticipants, Math.min(theme.maxParticipants, Number(e.target.value))))} + min={theme.minParticipants} + max={theme.maxParticipants} + /> +
+
+
+ +