Skip to content

Commit cdfed22

Browse files
committed
refac: QR 인증 에러핸들링
1 parent 3b2006b commit cdfed22

File tree

2 files changed

+77
-75
lines changed

2 files changed

+77
-75
lines changed

src/features/ticket/hooks/useOrderHook.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ export const useOrderTicket = () => {
5050
// qr 스캔
5151
export const useTicketQrCodeValidate = () => {
5252
return useMutation({
53-
mutationFn: ({ orderId, sig }: { orderId: number; sig: string }) =>
54-
ticketQrCode(orderId, sig),
53+
mutationFn: ({ orderId, sig }: { orderId: number; sig: string }) => ticketQrCode(orderId, sig),
5554
onSuccess: () => {
5655
alert('체크인 성공!');
5756
},
58-
onError: () => {
59-
alert('체크인 실패했습니다. 다시 시도해주세요.');
57+
onError: (error: any) => {
58+
const errorCode = error?.code;
59+
if (errorCode === 'QR_CODE4001') {
60+
alert('이미 사용된 QR 코드입니다.');
61+
} else if (errorCode === 'QR_CODE4002') {
62+
alert('잘못된 QR 코드 형식입니다.');
63+
} else {
64+
alert('QR 코드 검증 중 오류가 발생했습니다.');
65+
}
6066
},
6167
});
62-
};
68+
};
Lines changed: 66 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,79 @@
1-
import { useEffect, useRef, useState } from "react";
2-
import QrScanner from "qr-scanner";
3-
import { useTicketQrCodeValidate } from "../hooks/useOrderHook";
4-
import Button from "../../../../design-system/ui/Button";
1+
import { useEffect, useRef, useState } from 'react';
2+
import QrScanner from 'qr-scanner';
3+
import { useTicketQrCodeValidate } from '../hooks/useOrderHook';
4+
import Button from '../../../../design-system/ui/Button';
55

66
const QrScannerComponent = () => {
7-
const videoRef = useRef<HTMLVideoElement>(null);
8-
const [isScanning, setIsScanning] = useState(false);
9-
const qrScannerRef = useRef<QrScanner | null>(null);
7+
const videoRef = useRef<HTMLVideoElement>(null);
8+
const [isScanning, setIsScanning] = useState(false);
9+
const qrScannerRef = useRef<QrScanner | null>(null);
1010

11-
const { mutate: validateQr } = useTicketQrCodeValidate();
11+
const { mutate: validateQr } = useTicketQrCodeValidate();
1212

13-
useEffect(() => {
14-
if (!videoRef.current) return;
13+
useEffect(() => {
14+
if (!videoRef.current) return;
1515

16-
const scanner = new QrScanner(
17-
videoRef.current,
18-
(decoded) => {
19-
const data = decoded.data;
20-
setIsScanning(false);
21-
scanner.stop();
22-
checkInApiCall(data);
23-
},
24-
{
25-
onDecodeError: (err) => {
26-
console.warn("QR decode error:", err);
27-
},
28-
highlightScanRegion: true,
29-
maxScansPerSecond: 5,
30-
}
31-
);
32-
33-
qrScannerRef.current = scanner;
16+
const scanner = new QrScanner(
17+
videoRef.current,
18+
decoded => {
19+
const data = decoded.data;
20+
setIsScanning(false);
21+
scanner.stop();
22+
checkInApiCall(data);
23+
},
24+
{
25+
onDecodeError: err => {
26+
console.warn('QR decode error:', err);
27+
},
28+
highlightScanRegion: true,
29+
maxScansPerSecond: 5,
30+
}
31+
);
3432

35-
return () => {
36-
scanner.destroy();
37-
qrScannerRef.current = null;
38-
};
39-
}, []);
33+
qrScannerRef.current = scanner;
4034

41-
const handleStartScan = async () => {
42-
try {
43-
await navigator.mediaDevices.getUserMedia({ video: true });
44-
qrScannerRef.current?.start();
45-
setIsScanning(true);
46-
} catch (e) {
47-
alert(
48-
"카메라 접근이 차단되어 있어 스캔할 수 없습니다.\n시스템 설정 또는 브라우저 설정에서 권한을 허용해 주세요."
49-
);
50-
}
35+
return () => {
36+
scanner.destroy();
37+
qrScannerRef.current = null;
5138
};
39+
}, []);
5240

53-
const checkInApiCall = async (qrData: string) => {
54-
try {
55-
const params = new URLSearchParams(qrData.replace(/-/g, '='));
56-
const orderIdStr = params.get('orderId');
57-
const sig = params.get('sig');
58-
const orderId = orderIdStr ? parseInt(orderIdStr, 10) : NaN;
41+
const handleStartScan = async () => {
42+
try {
43+
await navigator.mediaDevices.getUserMedia({ video: true });
44+
qrScannerRef.current?.start();
45+
setIsScanning(true);
46+
} catch {
47+
alert(
48+
'카메라 접근이 차단되어 있어 스캔할 수 없습니다.\n시스템 설정 또는 브라우저 설정에서 권한을 허용해 주세요.'
49+
);
50+
}
51+
};
5952

60-
if (!isNaN(orderId) && sig) {
61-
validateQr({ orderId, sig });
62-
} else {
63-
alert("QR 코드 데이터 형식이 올바르지 않습니다.");
64-
}
65-
} catch {
66-
alert("QR 코드 데이터를 파싱할 수 없습니다.");
67-
}
68-
};
53+
const checkInApiCall = async (qrData: string) => {
54+
try {
55+
const params = new URLSearchParams(qrData.replace(/-/g, '='));
56+
const orderIdStr = params.get('orderId');
57+
const sig = params.get('sig');
58+
const orderId = orderIdStr ? parseInt(orderIdStr, 10) : NaN;
6959

70-
return (
71-
<div className="p-4">
72-
<video
73-
ref={videoRef}
74-
className="border rounded w-full max-w-md"
75-
style={{ aspectRatio: "3 / 4" }}
76-
/>
77-
{!isScanning && (
78-
<Button label="스캔하기" onClick={handleStartScan} className="w-full h-12 rounded-full mt-10 px-4 py-2" />
79-
)}
80-
</div>
81-
);
60+
if (!isNaN(orderId) && sig) {
61+
validateQr({ orderId, sig });
62+
} else {
63+
alert('QR 코드 데이터 형식이 올바르지 않습니다.');
64+
}
65+
} catch {
66+
alert('QR 코드 데이터를 파싱할 수 없습니다.');
67+
}
68+
};
69+
70+
return (
71+
<div className="p-4">
72+
<video ref={videoRef} className="border rounded w-full max-w-md" style={{ aspectRatio: '3 / 4' }} />
73+
{!isScanning && (
74+
<Button label="스캔하기" onClick={handleStartScan} className="w-full h-12 rounded-full mt-10 px-4 py-2" />
75+
)}
76+
</div>
77+
);
8278
};
8379
export default QrScannerComponent;

0 commit comments

Comments
 (0)