Skip to content

Commit 3fffe07

Browse files
authored
[SOC-202] 예매 취소 api연결 완료, 예매 취소와 관련된 것들 변경 (#198)
* feat: Add cancel * feat: Add cancel * feat: Update cancel ticket
1 parent d15f5b5 commit 3fffe07

File tree

9 files changed

+128
-66
lines changed

9 files changed

+128
-66
lines changed

src/api/orders/ordersApi.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,20 @@ import axios from "axios";
22
import {
33
GetAllOrderResponse,
44
GetOneOrderResponse,
5-
NewOrder,
6-
NewOrderResponse,
75
} from "../../types/api/order";
86
import { baseURL } from "../../constants/api";
7+
import { ApiResponse } from "../../types/api/common";
98

109
const API_URL = baseURL + "orders/";
1110

1211
const api = axios.create({
1312
baseURL: API_URL,
1413
});
1514

16-
const createNewOrder = async ({
17-
eventId,
18-
eventDateId,
19-
seatIds,
20-
}: NewOrder): Promise<NewOrderResponse> => {
21-
const response = await api.post<NewOrderResponse>(API_URL, {
22-
eventId,
23-
eventDateId,
24-
seatIds,
25-
});
15+
const cancelOrder = async (orderId: string): Promise<ApiResponse<"">> => {
16+
const response = await api.post<ApiResponse<"">>(
17+
API_URL + `${orderId}/cancel`
18+
);
2619
return response.data;
2720
};
2821

@@ -54,4 +47,4 @@ api.interceptors.request.use(
5447
}
5548
);
5649

57-
export { createNewOrder, getAllOrder, getOneOrder };
50+
export { cancelOrder, getAllOrder, getOneOrder };

src/components/organisms/reservation/ReservationSeatInfo.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ const ReservationSeatInfo = (eventData: Event) => {
4747
<>
4848
<Button
4949
onClick={() => void handleReservationSocketSubmit()}
50-
className="p-4 w-full transition-colors "
50+
className="p-4 w-full transition-colors flex items-center justify-between"
5151
variant="primary"
5252
>
53-
선택 좌석 예매하기
53+
<span>선택 좌석 예매하기</span>
54+
<div className="size-6 text-sm rounded-full flex items-center justify-center text-black bg-gray-300">
55+
{selectedSeats.length}
56+
</div>
5457
</Button>
5558
{selectedSeats.map((seat) => (
5659
<div

src/components/pages/MyDetailPage.tsx

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,82 @@
1-
import { useLocation, useNavigate } from "react-router-dom";
1+
import { useNavigate, useParams } from "react-router-dom";
22
import MainLayout from "../layout/MainLayout";
3-
import { GetOrder } from "../../types/api/order";
3+
import { GetOneOrderResponse } from "../../types/api/order";
44
import Font from "../atoms/fonts/Font";
5-
import { fetchErrorMessages } from "../../constants/errorMessages";
5+
import {
6+
fetchErrorMessages,
7+
cancelOrderErrorMessages,
8+
} from "../../constants/errorMessages";
69
import { formatToKoreanDateAndTime } from "../../utils/dateUtils";
710
import Button from "../atoms/buttons/Button";
8-
import { useState } from "react";
11+
import { useContext, useState } from "react";
12+
import { cancelOrder, getOneOrder } from "../../api/orders/ordersApi";
13+
import { toast } from "react-toastify";
14+
import { UserContext } from "../../store/UserContext";
15+
import { useQueryClient } from "@tanstack/react-query";
16+
import { createResourceQuery } from "../../hooks/useCustomQuery";
917

1018
const MyDetailPage = () => {
11-
const location = useLocation();
19+
const { orderId } = useParams<{ orderId: string }>();
20+
const queryClient = useQueryClient();
21+
1222
const navigate = useNavigate();
13-
const state = location.state as { order: GetOrder };
14-
const order = state.order;
15-
const [isCancleModalOpen, setIsCancleModalOpen] = useState(false);
23+
const { userId } = useContext(UserContext);
24+
const [isCancelModalOpen, setIsCancelModalOpen] = useState(false);
1625
const [isShowModalOpen, setIsShowModalOpen] = useState(false);
17-
if (!order) {
18-
return <div>{fetchErrorMessages.noReservationData}</div>;
19-
}
26+
const useOneOrder = createResourceQuery<GetOneOrderResponse>(
27+
`my-order-${userId}`, // 쿼리 키의 기본 이름
28+
(orderId) => getOneOrder(orderId) // fetchFn으로 getAllOrder 사용
29+
);
30+
const { data, isLoading, isError } = useOneOrder(orderId);
2031

32+
const order = data?.data;
33+
if (isLoading) return <p>{fetchErrorMessages.isLoading}</p>;
34+
if (isError) return <p>{fetchErrorMessages.general}</p>;
35+
if (!order) return <p>{fetchErrorMessages.noReservationData}</p>;
36+
37+
const handleCancelOrder = async () => {
38+
try {
39+
const response = await cancelOrder(orderId!);
40+
if (response.code === 0) {
41+
toast.success(cancelOrderErrorMessages.success);
42+
} else {
43+
switch (response.code) {
44+
case 8:
45+
toast.error(cancelOrderErrorMessages.unauthorized);
46+
break;
47+
case 15:
48+
toast.error(cancelOrderErrorMessages.notFound);
49+
break;
50+
case 22:
51+
toast.error(cancelOrderErrorMessages.alreadyCanceled);
52+
break;
53+
case 6:
54+
toast.error(cancelOrderErrorMessages.internalServerError);
55+
break;
56+
default:
57+
toast.error("알 수 없는 오류가 발생했습니다.");
58+
}
59+
}
60+
} catch (error) {
61+
console.log(error);
62+
toast.error("취소 처리 중 오류가 발생했습니다.");
63+
}
64+
};
2165
// 모달 열기
22-
const openCancleModal = () => setIsCancleModalOpen(true);
66+
const openCancelModal = () => setIsCancelModalOpen(true);
2367
const openShowModal = () => setIsShowModalOpen(true);
2468

2569
// 모달 닫기
26-
const closeCancleModal = () => setIsCancleModalOpen(false);
70+
const closeCancelModal = () => setIsCancelModalOpen(false);
2771
const closeShowModal = () => setIsShowModalOpen(false);
2872

2973
// 예매 취소 확인
30-
const handleCancelReservation = () => {
31-
closeCancleModal(); // 모달 닫기
74+
const handleCancelReservation = async () => {
75+
closeCancelModal(); // 모달 닫기
76+
await handleCancelOrder();
77+
await queryClient.invalidateQueries({
78+
queryKey: [`my-orders-${userId}`],
79+
}); // orders 쿼리 무효화
3280
navigate("/mypage"); // 마이페이지로 이동
3381
};
3482

@@ -123,15 +171,15 @@ const MyDetailPage = () => {
123171
{/* 예매 취소 버튼 */}
124172
<div className="fixed bottom-0 right-8 md:left-0 md:right-0 pb-4 flex justify-center">
125173
<Button
126-
onClick={openCancleModal}
174+
onClick={openCancelModal}
127175
className="bg-se-500 text-white px-6 py-3 rounded-lg hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-300"
128176
>
129177
예매 취소
130178
</Button>
131179
</div>
132180
</div>
133181
{/* 모달 */}
134-
{isCancleModalOpen && (
182+
{isCancelModalOpen && (
135183
<div className="fixed inset-0 flex items-center justify-center bg-black/50 z-50">
136184
<div className="bg-white rounded-lg shadow-lg p-6 w-96">
137185
<h2 className="text-xl font-bold mb-4">예매 취소</h2>
@@ -141,15 +189,20 @@ const MyDetailPage = () => {
141189
<div className="flex justify-end space-x-4">
142190
<Button
143191
size="sm"
144-
onClick={handleCancelReservation}
192+
onClick={() => {
193+
handleCancelReservation().catch((error) => {
194+
console.error("취소 처리 중 오류 발생:", error);
195+
toast.error("취소 처리 중 문제가 발생했습니다.");
196+
});
197+
}}
145198
className="px-4 py-2 rounded-lg bg-red-500 text-white hover:bg-red-600"
146199
>
147200
예매 취소
148201
</Button>
149202
<Button
150203
size="sm"
151204
variant="secondary"
152-
onClick={closeCancleModal}
205+
onClick={closeCancelModal}
153206
className="px-4 py-2 rounded-lg border border-gray-300 hover:bg-gray-100"
154207
>
155208
뒤로 가기

src/components/pages/MyPageUser.tsx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const MyPageUser = () => {
3737
const upcomingEvents = reservationData.filter(
3838
(reservation) => new Date(reservation.eventDate) >= currentTime
3939
);
40+
4041
const renderReservationList = (events: GetOrder[], emptyMessage: string) => (
4142
<div className="mb-4">
4243
<ul className="space-y-4">
@@ -48,67 +49,65 @@ const MyPageUser = () => {
4849
<Button onClick={() => navigate("/")}>이벤트 보러가기</Button>
4950
</div>
5051
) : (
51-
events.map((reservation) => (
52+
events.map((order) => (
5253
<li
53-
key={reservation.orderId}
54+
key={order.orderId}
5455
className="p-4 px-6 border border-gray-300 rounded-lg shadow-sm flex flex-col md:flex-row md:items-center space-x-4"
5556
>
5657
<div className="flex justify-around items-start m-2">
5758
<img
58-
src={reservation.eventThumbnail}
59-
alt={reservation.eventTitle}
59+
src={order.eventThumbnail}
60+
alt={order.eventTitle}
6061
className="md:w-16 h-24 rounded-lg object-cover"
6162
/>
6263
</div>
6364
<div className="flex-1 pl-3">
6465
<h3 className="text-lg font-bold text-gray-700 mb-1">
65-
{reservation.eventTitle}
66+
{order.eventTitle}
6667
</h3>
6768
<p className="text-sm text-gray-500">
6869
<span className="inline-block w-8 md:w-14 font-semibold">
6970
예매
7071
</span>
71-
{formatToKoreanDateAndTime(reservation.orderCreatedAt)}
72+
{formatToKoreanDateAndTime(order.orderCreatedAt)}
7273
</p>
7374
<p className="text-sm text-gray-500">
7475
<span className="inline-block w-8 md:w-14 font-semibold">
7576
일정
7677
</span>
77-
{formatToKoreanDateAndTime(reservation.eventDate)}
78+
{formatToKoreanDateAndTime(order.eventDate)}
7879
</p>
7980
<p className="text-sm text-gray-500">
8081
<span className="inline-block w-8 md:w-14 font-semibold">
8182
장소
8283
</span>
83-
{reservation.eventPlace}
84+
{order.eventPlace}
8485
</p>
8586
<p className="text-sm text-gray-500">
8687
<span className="inline-block w-8 md:w-14 font-semibold">
8788
출연
8889
</span>
89-
{reservation.eventCast}
90+
{order.eventCast}
9091
</p>
9192
</div>
9293
<Button
93-
onClick={() =>
94-
navigate("/mypage/detail", {
95-
state: { order: reservation },
96-
})
97-
}
94+
onClick={() => navigate(`/mypage/detail/${order.orderId}`)}
9895
className="hidden md:inline-block"
96+
disabled={order.orderCanceledAt !== null}
9997
>
100-
예매 정보 보기
98+
{order.orderCanceledAt !== null
99+
? "취소된 티켓"
100+
: "예매 정보 보기"}
101101
</Button>
102102
<Button
103-
onClick={() =>
104-
navigate("/mypage/detail", {
105-
state: { order: reservation },
106-
})
107-
}
103+
onClick={() => navigate(`/mypage/detail/${order.orderId}`)}
108104
size="sm"
109105
className="mt-3 md:hidden"
106+
disabled={order.orderCanceledAt !== null}
110107
>
111-
예매 정보 보기
108+
{order.orderCanceledAt !== null
109+
? "취소된 티켓"
110+
: "예매 정보 보기"}
112111
</Button>
113112
</li>
114113
))

src/components/pages/OrderPage.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getUserPoints } from "../../api/users/usersApi";
1212
import { formatToKoreanDateAndTime } from "../../utils/dateUtils";
1313
import { useReservationContext } from "../../store/ReservationContext";
1414
import { UserContext } from "../../store/UserContext";
15+
import { useQueryClient } from "@tanstack/react-query";
1516

1617
const OrderPage = () => {
1718
const navigate = useNavigate();
@@ -22,15 +23,15 @@ const OrderPage = () => {
2223
};
2324
const orderData = state.orderData;
2425
const eventData = state.eventData;
25-
26+
const queryClient = useQueryClient();
2627
const { userId } = useContext(UserContext);
2728
const { requestOrder, socket } = useReservationContext();
2829

2930
const [isAgreed, setIsAgreed] = useState(false); // 구매 동의 체크박스 상태
3031
const [userPoints, setUserPoints] = useState<number>(-1);
3132
const [paymentMethod, setPaymentMethod] = useState<string | null>(null); // 선택된 결제 방법
3233

33-
const handlePayment = () => {
34+
const handlePayment = async () => {
3435
if (!isAgreed) {
3536
toast.error("구매조건 확인 및 결제 진행에 동의해주세요!");
3637
return;
@@ -74,9 +75,9 @@ const OrderPage = () => {
7475
// if (response.code === 0) {
7576
// toast.success("결제가 진행됩니다!");
7677

77-
// await queryClient.invalidateQueries({
78-
// queryKey: [`my-orders-${userId}`],
79-
// }); // orders 쿼리 무효화
78+
await queryClient.invalidateQueries({
79+
queryKey: [`my-orders-${userId}`],
80+
}); // orders 쿼리 무효화
8081
socket.on("orderApproved", (response: ApprovedOrderResponse) => {
8182
navigate(`/reservation-confirmation`, {
8283
state: { paymentData: response.data },
@@ -246,7 +247,10 @@ const OrderPage = () => {
246247
</div>
247248
<Button
248249
onClick={() => {
249-
handlePayment();
250+
handlePayment().catch((error) => {
251+
console.error("결제 처리 중 오류 발생:", error);
252+
toast.error("결제 처리 중 문제가 발생했습니다.");
253+
});
250254
}}
251255
className="text-sm w-full"
252256
>

src/components/templates/reservation-overview/ReservationConfirmationTemplate.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ const ReservationConfirmationTemplate = ({ data }: ReservationConfirmProps) => {
3838
{/* User Info */}
3939
<div className="flex items-center space-x-4 p-4 bg-gray-50 rounded-lg">
4040
<div className="w-12 h-12 bg-rose-100 rounded-full flex items-center justify-center">
41-
<span className="font-bold">{data.userEmail}</span>
41+
<span className="font-bold">{data.userEmail.slice(0, 1)}</span>
4242
</div>
4343
<div>
4444
<Font className="font-bold text-gray-800">
45-
{data.userEmail}
45+
{data.userEmail.slice(0, 3)}
4646
</Font>
4747
</div>
4848
</div>

src/constants/errorMessages.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const tokenErrorMessage = "로그인 세션이 만료되었습니다. 다시 로그인해주세요.";
2+
13
export const registerErrorMessages = {
24
validation: {
35
emailInvalid: "이메일 형식이 올바르지 않습니다.",
@@ -7,6 +9,15 @@ export const registerErrorMessages = {
79
generic: "회원가입에 실패하였습니다.",
810
};
911

12+
export const cancelOrderErrorMessages = {
13+
success: "주문이 성공적으로 취소되었습니다.",
14+
unauthorized: tokenErrorMessage,
15+
notFound: "티켓을 찾을 수 없습니다.",
16+
alreadyCanceled: "이 티켓은 이미 취소되었습니다.",
17+
internalServerError:
18+
"서버 내부 오류가 발생했습니다. 나중에 다시 시도해주세요.",
19+
};
20+
1021
export const loginErrorMessages = {
1122
validation: {
1223
emailInvalid: "이메일을 입력해주세요.",
@@ -25,8 +36,6 @@ export const fetchErrorMessages = {
2536
noReservationData: "오류 발생: 예약 정보를 불러올 수 없습니다.",
2637
};
2738

28-
const tokenErrorMessage = "로그인 세션이 만료되었습니다. 다시 로그인해주세요.";
29-
3039
export const postEventErrorMessages = {
3140
invalidToken: tokenErrorMessage,
3241
general: "공연 등록에 실패하였습니다. 다시 시도해주세요.",

src/shared/Router.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const Router = () => {
5454
path="manager/:eventId/:eventDateId"
5555
element={<WrappedManagerDetailPage />}
5656
/>
57-
<Route path="mypage/detail" element={<MyDetailPage />} />
57+
<Route path="mypage/detail/:orderId" element={<MyDetailPage />} />
5858
</Routes>
5959
</BrowserRouter>
6060
);

0 commit comments

Comments
 (0)