Skip to content

Commit c8641e1

Browse files
committed
2 parents 1ab0fb6 + aea8c9e commit c8641e1

11 files changed

Lines changed: 114 additions & 59 deletions

File tree

apps/ticket-admin/src/lib/axios/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from "axios";
22

3-
import { API_URL } from "@/data/constants";
43
import { EXTERNAL_PATH } from "@/shared/constants/path";
54
import { IS_LOGINED } from "@/shared/constants/storage";
65
import { isAxiosErrorResponse } from "@/shared/types/axioxError";
76

8-
import { safeLocalStorage } from "../storage";
7+
import { safeSessionStorage } from "../storage";
98
import { refreshAccessToken } from "./helpers";
109
import { ERROR_CODE } from "./utils/errorCode";
1110

@@ -48,7 +47,6 @@ instance.interceptors.response.use(
4847
// 엑세스 토큰 없음
4948
if (
5049
error.response?.data.code === ERROR_CODE.NO_ACCESS_TOKEN ||
51-
error.response?.data.code === ERROR_CODE.ACCESS_TOKEN_EXPIRED ||
5250
error.response?.data.code === ERROR_CODE.REFRESH_TOKEN_EXPIRED
5351
) {
5452
redirectToLoginOnce();
@@ -111,9 +109,11 @@ function redirectToLoginOnce() {
111109
isLoginAlertShown = true;
112110

113111
alert("로그인이 필요한 페이지입니다.");
114-
safeLocalStorage.remove(IS_LOGINED);
112+
safeSessionStorage.remove(IS_LOGINED);
115113

116-
const redirectUrl = `${EXTERNAL_PATH.LOGIN}?redirectUrl=${encodeURIComponent(window.location.pathname)}`;
114+
const redirectUrl = `${EXTERNAL_PATH.LOGIN}?redirectUrl=${encodeURIComponent(
115+
window.location.origin + window.location.pathname + window.location.search,
116+
)}`;
117117

118118
window.location.href = redirectUrl;
119119
}

apps/ticket/next.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const nextConfig: NextConfig = {
4242
images: {
4343
domains: [
4444
process.env.NEXT_PUBLIC_CDN_DOMAIN || "",
45+
process.env.NEXT_PUBLIC_CDN_NOTION_DOMAIN || "",
4546
"avatars.githubusercontent.com",
4647
"scontent-icn2-1.cdninstagram.com",
4748
"images.unsplash.com",

apps/ticket/src/app/(pages)/auth/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useCallback, useEffect } from "react";
44
import { useRouter, useSearchParams } from "next/navigation";
55

66
import { useLoginMutation } from "@/data/users/postUserLogin/mutation";
7-
import { safeLocalStorage } from "@/lib/storage";
7+
import { safeLocalStorage, safeSessionStorage } from "@/lib/storage";
88
import { LoadingWithLayout } from "@/shared/components/LoadingWithLayout";
99
import { PATH } from "@/shared/constants/path";
1010
import {
@@ -32,7 +32,7 @@ const AuthPage = () => {
3232
const authorizationCode = searchParams.get("code");
3333

3434
const socialType = safeLocalStorage.get(SOCIAL_LOGIN_TYPE_KEY) as SocialLoginType;
35-
const redirectUrl = safeLocalStorage.get(REDIRECT_URL_KEY);
35+
const redirectUrl = safeSessionStorage.get(REDIRECT_URL_KEY);
3636

3737
const { mutateAsync } = useLoginMutation();
3838

@@ -47,7 +47,7 @@ const AuthPage = () => {
4747
safeLocalStorage.set(IS_LOGINED, "true");
4848

4949
router.replace(redirectUrl || PATH.HOME);
50-
safeLocalStorage.remove(REDIRECT_URL_KEY);
50+
safeSessionStorage.remove(REDIRECT_URL_KEY);
5151
} catch (error) {
5252
safeLocalStorage.set(TOKEN_KEY, (error as Error).message);
5353
router.replace(PATH.SIGNUP);

apps/ticket/src/app/(pages)/staff/ticket-authorization/_clientBoundary/TicketAuthorizationClient/index.tsx

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import classNames from "classnames/bind";
77
import { Flex, Typography } from "@permit/design-system";
88
import { useGuestTicketDoorValidationQuery } from "@/data/tickets/getGuestTicketDoorValidation/queries";
99
import { useTicketDoorValidationQuery } from "@/data/tickets/getTicketDoorValidation/queries";
10+
import { useGuestTicketCameraConfirmMutation } from "@/data/tickets/postStaffGuestTicketDoorConfirm/mutation";
11+
import { useUserTicketCameraConfirmMutation } from "@/data/tickets/postStaffTicketDoorConfirm/mutation";
1012
import { isAxiosErrorResponse } from "@/shared/types/axioxError";
1113

1214
import styles from "./index.module.scss";
@@ -143,6 +145,9 @@ export const TicketAuthorizationClient = () => {
143145
const qrCodeRef = useRef<any>(null);
144146
const scanningRef = useRef(false);
145147

148+
const { mutateAsync: userTicketCameraMutate } = useUserTicketCameraConfirmMutation();
149+
const { mutateAsync: guestTicketCameraMutate } = useGuestTicketCameraConfirmMutation();
150+
146151
// html5-qrcode 스타일 주입
147152
useEffect(() => {
148153
const styleId = "qr-reader-styles";
@@ -278,48 +283,47 @@ export const TicketAuthorizationClient = () => {
278283

279284
// 검증 결과 처리
280285
useEffect(() => {
281-
console.log(scannedTicketCode, scanningRef.current);
282-
283-
if (!scannedTicketCode || !scanningRef.current) {
284-
return;
285-
}
286-
287-
// staff 권한으로 ticketCode 에 대해서 입장 가능하다는 API 를 쏘면 해당 티켓 체크
288-
289-
// if (error) {
290-
// if (isAxiosErrorResponse(error)) {
291-
// let message = "티켓 검증에 실패했습니다.";
292-
293-
// if (error.code === NO_ENTRY_TIME) {
294-
// message = "해당 티켓의 유효 시간이 아닙니다.";
295-
// } else if (error.code === ALREADY_USED_TICKET) {
296-
// message = "이미 사용한 티켓입니다.";
297-
// } else if (error.code === CANCELED_TICKET) {
298-
// message = "취소된 티켓입니다.";
299-
// } else if (error.message) {
300-
// message = error.message;
301-
// }
302-
303-
// showToast(message, "error");
304-
// } else {
305-
// showToast("티켓 검증에 실패했습니다.", "error");
306-
// }
286+
if (!scannedTicketCode || !scanningRef.current) return;
307287

308-
// 검증 실패 후 스캔 재개
309-
setTimeout(() => {
310-
scanningRef.current = false;
311-
setScannedTicketCode(null);
312-
}, 2000);
313-
314-
showToast(`확인되었습니다.`, "success");
288+
const verifyTicket = async () => {
289+
try {
290+
if (isGuest) {
291+
await guestTicketCameraMutate({ ticketCode: scannedTicketCode });
292+
} else {
293+
await userTicketCameraMutate({ ticketCode: scannedTicketCode });
294+
}
295+
296+
// ✅ 성공
297+
showToast("확인되었습니다.", "success");
298+
} catch (error) {
299+
// ❌ 실패
300+
let message = "티켓 검증에 실패했습니다.";
301+
302+
if (isAxiosErrorResponse(error)) {
303+
if (error.code === NO_ENTRY_TIME) {
304+
message = "해당 티켓의 유효 시간이 아닙니다.";
305+
} else if (error.code === ALREADY_USED_TICKET) {
306+
message = "이미 사용한 티켓입니다.";
307+
} else if (error.code === CANCELED_TICKET) {
308+
message = "취소된 티켓입니다.";
309+
} else if (error.message) {
310+
message = error.message;
311+
}
312+
}
313+
314+
showToast(message, "error");
315+
} finally {
316+
// 🔄 성공/실패 상관없이 스캔 재개
317+
setTimeout(() => {
318+
scanningRef.current = false;
319+
setScannedTicketCode(null);
320+
setLastScannedCode(null);
321+
}, 2000);
322+
}
323+
};
315324

316-
// 검증 성공 후 스캔 재개
317-
setTimeout(() => {
318-
scanningRef.current = false;
319-
setScannedTicketCode(null);
320-
setLastScannedCode(null);
321-
}, 2000);
322-
}, [scannedTicketCode, isGuest]);
325+
verifyTicket();
326+
}, [scannedTicketCode, isGuest, guestTicketCameraMutate, userTicketCameraMutate]);
323327

324328
return (
325329
<div className={cx("container")}>

apps/ticket/src/data/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,9 @@ export const API_URL = {
6666
CONFIRM: "/api/tickets/door/staff/confirm",
6767
/** 도어용 게스트 스탭 티켓 확인 API */
6868
GUEST_CONFIRM: "/api/guests/tickets/door/staff/confirm",
69+
/** 도어용 유저 스탭 카메라 확인 API */
70+
CAMERA_USER_TICKET_CONFIRM: "/api/staff/tickets/door/confirm",
71+
/** 도어용 게스트 스탭 카메라 확인 API */
72+
CAMERA_GUEST_TICKET_CONFIRM: "/api/staff/guests/door/confirm",
6973
},
7074
} as const;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
3+
import { API_URL } from "@/data/constants";
4+
import { instance } from "@/lib/axios";
5+
import { AxiosErrorResponse } from "@/shared/types/axioxError";
6+
import { UsePermitMutationOptions } from "@/shared/types/queryOptions";
7+
8+
import { StaffGuestTicketConfirmRequest } from "./types";
9+
10+
/** 도어용 게스트 티켓 카메라 확인 API */
11+
export const useGuestTicketCameraConfirmMutation = (
12+
options?: UsePermitMutationOptions<null, AxiosErrorResponse, StaffGuestTicketConfirmRequest>,
13+
) => {
14+
return useMutation({
15+
mutationFn: (params) =>
16+
instance.post(API_URL.TICKET.CAMERA_GUEST_TICKET_CONFIRM, params).then((res) => res.data),
17+
...options,
18+
});
19+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type StaffGuestTicketConfirmRequest = {
2+
/** 티켓고유코드 */
3+
ticketCode: string;
4+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
3+
import { API_URL } from "@/data/constants";
4+
import { instance } from "@/lib/axios";
5+
import { AxiosErrorResponse } from "@/shared/types/axioxError";
6+
import { UsePermitMutationOptions } from "@/shared/types/queryOptions";
7+
8+
import { StaffTicketConfirmRequest } from "./types";
9+
10+
/** 도어용 유저 티켓 카메라 확인 API */
11+
export const useUserTicketCameraConfirmMutation = (
12+
options?: UsePermitMutationOptions<null, AxiosErrorResponse, StaffTicketConfirmRequest>,
13+
) => {
14+
return useMutation({
15+
mutationFn: (params) =>
16+
instance.post(API_URL.TICKET.CAMERA_USER_TICKET_CONFIRM, params).then((res) => res.data),
17+
...options,
18+
});
19+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type StaffTicketConfirmRequest = {
2+
/** 티켓고유코드 */
3+
ticketCode: string;
4+
};

apps/ticket/src/lib/axios/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PATH } from "@/shared/constants/path";
55
import { IS_LOGINED } from "@/shared/constants/storage";
66
import { AxiosErrorResponse, isAxiosErrorResponse } from "@/shared/types/axioxError";
77

8-
import { safeLocalStorage } from "../storage";
8+
import { safeLocalStorage, safeSessionStorage } from "../storage";
99
import { refreshAccessToken } from "./helpers";
1010
import { ERROR_CODE } from "./utils/errorCode";
1111

@@ -37,25 +37,25 @@ instance.interceptors.response.use(
3737
async (error: AxiosError<AxiosErrorResponse>) => {
3838
if (typeof window === "undefined") {
3939
// Server에서는 기본 전파
40-
console.error(error);
40+
// console.error(error);
4141

4242
return Promise.reject(error);
4343
}
4444

45-
console.error("##error", JSON.stringify(error));
45+
// console.error("##error", JSON.stringify(error));
46+
47+
if (error.config?.url === API_URL.USER.LOGOUT) {
48+
safeLocalStorage.remove(IS_LOGINED);
49+
window.location.href = PATH.LOGIN;
50+
}
4651

4752
// 로그인이 필요한 요청이거나, 리프레시 토큰 모두 만료시 로그인 페이지로 이동
4853
if (isAxiosErrorResponse(error.response?.data)) {
4954
// 엑세스 토큰 없음
5055
if (
5156
error.response?.data.code === ERROR_CODE.NO_ACCESS_TOKEN ||
52-
error.response?.data.code === ERROR_CODE.ACCESS_TOKEN_EXPIRED ||
5357
error.response?.data.code === ERROR_CODE.REFRESH_TOKEN_EXPIRED
5458
) {
55-
if (error.config?.url === API_URL.USER.LOGOUT) {
56-
safeLocalStorage.remove(IS_LOGINED);
57-
}
58-
5959
redirectToLoginOnce();
6060
}
6161

@@ -121,7 +121,7 @@ function redirectToLoginOnce() {
121121
isLoginAlertShown = true;
122122

123123
alert("로그인이 필요한 페이지입니다.");
124-
safeLocalStorage.remove(IS_LOGINED);
124+
safeSessionStorage.remove(IS_LOGINED);
125125

126126
const redirectUrl = window.location.pathname + window.location.search;
127127

0 commit comments

Comments
 (0)