From 5382b042ee1800691e4b9974657fbb19bf15de72 Mon Sep 17 00:00:00 2001 From: Yejiin21 <101397075+Yejiin21@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:09:39 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refac:=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80,=20=EC=82=AD=EC=A0=9C=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/host/hook/useHostHook.ts | 17 ++++++++--- src/features/host/hook/useHostInvitation.ts | 6 ++-- src/features/host/hook/useInviteHostHook.ts | 34 +++++++++++++++------ src/features/host/model/host.ts | 5 +++ src/features/host/model/hostInvitation.ts | 6 ++++ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/features/host/hook/useHostHook.ts b/src/features/host/hook/useHostHook.ts index f2c1ff1a..173f287f 100644 --- a/src/features/host/hook/useHostHook.ts +++ b/src/features/host/hook/useHostHook.ts @@ -1,15 +1,16 @@ import { useMutation } from '@tanstack/react-query'; -import { HostCreationRequest, UpdateHostChannelInfoRequest } from '../model/host'; +import { HostCreationRequest, HostDeletionResponse, UpdateHostChannelInfoRequest } from '../model/host'; import { ApiResponse } from '../../../shared/types/api/apiResponse'; import { createHost, deleteHost, updateHostInfo } from '../api/host'; +import { AxiosError } from 'axios'; export const useHostCreation = () => { return useMutation, Error, HostCreationRequest>({ mutationFn: async (requestBody: HostCreationRequest) => { return await createHost(requestBody); }, - onError: (error) => { - console.log("error", error.message); + onError: error => { + console.log('error', error.message); }, }); }; @@ -22,9 +23,17 @@ export const useUpdateHostChannelInfo = (hostChannelId: number) => { }; export const useHostDeletion = () => { - return useMutation, Error, number>({ + return useMutation, AxiosError, number>({ mutationFn: async (hostChannelId: number) => { return await deleteHost(hostChannelId); }, + onError: error => { + const code = error.response?.data?.code; + + if (code === 'HOST_CHANNEL4002' || code === 'HOST_CHANNEL4005') { + const message = error.response?.data?.message || '알 수 없는 오류가 발생했습니다.'; + alert(message); + } + }, }); }; diff --git a/src/features/host/hook/useHostInvitation.ts b/src/features/host/hook/useHostInvitation.ts index 33affa73..c93db57a 100644 --- a/src/features/host/hook/useHostInvitation.ts +++ b/src/features/host/hook/useHostInvitation.ts @@ -1,10 +1,10 @@ import { useMutation } from '@tanstack/react-query'; -import { ApiResponse } from '../../../shared/types/api/apiResponse'; -import { HostInvitationRequest } from '../model/hostInvitation'; +import { HostInvitationRequest, HostInvitationResponse } from '../model/hostInvitation'; import { inviteMember } from '../api/hostInvitation'; +import { AxiosError } from 'axios'; export const useHostInvitation = (hostChannelId: number) => { - return useMutation, Error, HostInvitationRequest>({ + return useMutation, HostInvitationRequest>({ mutationFn: async (requestBody: HostInvitationRequest) => { return await inviteMember(hostChannelId, requestBody); }, diff --git a/src/features/host/hook/useInviteHostHook.ts b/src/features/host/hook/useInviteHostHook.ts index e465f8ed..682c75b1 100644 --- a/src/features/host/hook/useInviteHostHook.ts +++ b/src/features/host/hook/useInviteHostHook.ts @@ -13,27 +13,41 @@ export const useInviteMembers = (hostChannelId: number) => { const invitationPromises = emails.map( email => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { inviteMember( { email }, { - onSuccess: resolve, - onError: reject, + onSuccess: () => resolve(), + onError: error => reject(error), } ); }) ); - Promise.all(invitationPromises) - .then(() => { + Promise.allSettled(invitationPromises).then(results => { + const failed = results.filter(r => r.status === 'rejected') as PromiseRejectedResult[]; + + if (failed.length > 0) { + console.log('🚨 first error:', failed[0].reason); + } + + if (failed.length === 0) { alert('초대가 전송되었습니다.'); queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] }); onSuccess?.(); - }) - .catch(() => { - alert('초대 중 일부 실패했습니다.'); - onError?.(); - }); + return; + } + + const firstError = failed[0].reason; + + const errorMessage = + firstError?.response?.data?.message || // AxiosError일 경우 + firstError?.message || // 일반 에러 객체일 경우 + '알 수 없는 오류가 발생했습니다.'; + + alert(errorMessage); + onError?.(); + }); }; return { inviteMembers }; diff --git a/src/features/host/model/host.ts b/src/features/host/model/host.ts index 045e9f11..f700e5c6 100644 --- a/src/features/host/model/host.ts +++ b/src/features/host/model/host.ts @@ -11,3 +11,8 @@ export interface UpdateHostChannelInfoRequest { hostEmail: string; channelDescription: string; } + +export interface HostDeletionResponse { + code: string; + message: string; +} diff --git a/src/features/host/model/hostInvitation.ts b/src/features/host/model/hostInvitation.ts index 15230cc6..69a14f1d 100644 --- a/src/features/host/model/hostInvitation.ts +++ b/src/features/host/model/hostInvitation.ts @@ -1,3 +1,9 @@ export interface HostInvitationRequest { email: string; } + +export interface HostInvitationResponse { + code: string; + message: string; + result?: string; +} From c33d2d54dfaa242a6bdd8051349b01715039ef89 Mon Sep 17 00:00:00 2001 From: Yejiin21 <101397075+Yejiin21@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:13:01 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refac:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=97=90=EB=9F=AC=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/event/hook/useEventHook.ts | 12 +++++++++++- src/entities/event/model/event.ts | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/entities/event/model/event.ts diff --git a/src/entities/event/hook/useEventHook.ts b/src/entities/event/hook/useEventHook.ts index d16a7ae2..87e8e5e7 100644 --- a/src/entities/event/hook/useEventHook.ts +++ b/src/entities/event/hook/useEventHook.ts @@ -4,6 +4,8 @@ import { useParams } from 'react-router-dom'; import { useUserInfo } from '../../../features/join/hooks/useUserHook'; import { ApiResponse } from '../../../shared/types/api/apiResponse'; import useAuthStore from '../../../app/provider/authStore'; +import { AxiosError } from 'axios'; +import { EventDeletionResponse } from '../model/event'; export const useEventDetail = () => { const { id } = useParams(); @@ -23,9 +25,17 @@ export const useEventDetail = () => { }; export const useEventDeletion = () => { - return useMutation, Error, number>({ + return useMutation, AxiosError, number>({ mutationFn: async (eventId: number) => { return await eventDeletion(eventId); }, + onError: error => { + const code = error.response?.data?.code; + + if (code === 'EVENT4002') { + const message = error.response?.data?.message || '알 수 없는 오류가 발생했습니다.'; + alert(message); + } + }, }); }; diff --git a/src/entities/event/model/event.ts b/src/entities/event/model/event.ts new file mode 100644 index 00000000..b97995a4 --- /dev/null +++ b/src/entities/event/model/event.ts @@ -0,0 +1,4 @@ +export interface EventDeletionResponse { + code: string; + message: string; +} From 3b2006b7b4d7747a5a730b137e6b5f890e47c927 Mon Sep 17 00:00:00 2001 From: Yejiin21 <101397075+Yejiin21@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:15:14 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/menu/ui/MyTicketPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/menu/ui/MyTicketPage.tsx b/src/pages/menu/ui/MyTicketPage.tsx index 5da22e48..f648a57c 100644 --- a/src/pages/menu/ui/MyTicketPage.tsx +++ b/src/pages/menu/ui/MyTicketPage.tsx @@ -8,11 +8,11 @@ import completedImg from '../../../../public/assets/menu/Completed.svg'; import pendingImg from '../../../../public/assets/menu/Pending.svg'; import ticketImg from '../../../../public/assets/menu/Ticket.svg'; import { useCancelTicket, useTicketOrders } from '../../../features/ticket/hooks/useOrderHook'; -import { OrderTicketResponse } from '../../../features/ticket/model/Order'; import EmailDeleteModal from '../../../widgets/dashboard/ui/email/EmailDeleteModal'; import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton'; import useAuthStore from '../../../app/provider/authStore'; import TextModal from '../../../shared/ui/TextModal'; +import { OrderTicketResponse } from '../../../features/ticket/model/Order'; const MyTicketPage = () => { const [isModalOpen, setIsModalOpen] = useState(false); From cdfed229838c3e6faf8edb8e814b7e8abd44d8a2 Mon Sep 17 00:00:00 2001 From: Yejiin21 <101397075+Yejiin21@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:20:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refac:=20QR=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=ED=95=B8=EB=93=A4=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/ticket/hooks/useOrderHook.ts | 16 ++- src/features/ticket/ui/QrScanner.tsx | 136 +++++++++++----------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/features/ticket/hooks/useOrderHook.ts b/src/features/ticket/hooks/useOrderHook.ts index f7d9a1dc..c65fcb0f 100644 --- a/src/features/ticket/hooks/useOrderHook.ts +++ b/src/features/ticket/hooks/useOrderHook.ts @@ -50,13 +50,19 @@ export const useOrderTicket = () => { // qr 스캔 export const useTicketQrCodeValidate = () => { return useMutation({ - mutationFn: ({ orderId, sig }: { orderId: number; sig: string }) => - ticketQrCode(orderId, sig), + mutationFn: ({ orderId, sig }: { orderId: number; sig: string }) => ticketQrCode(orderId, sig), onSuccess: () => { alert('체크인 성공!'); }, - onError: () => { - alert('체크인 실패했습니다. 다시 시도해주세요.'); + onError: (error: any) => { + const errorCode = error?.code; + if (errorCode === 'QR_CODE4001') { + alert('이미 사용된 QR 코드입니다.'); + } else if (errorCode === 'QR_CODE4002') { + alert('잘못된 QR 코드 형식입니다.'); + } else { + alert('QR 코드 검증 중 오류가 발생했습니다.'); + } }, }); -}; \ No newline at end of file +}; diff --git a/src/features/ticket/ui/QrScanner.tsx b/src/features/ticket/ui/QrScanner.tsx index 41835b0e..f4fb34ce 100644 --- a/src/features/ticket/ui/QrScanner.tsx +++ b/src/features/ticket/ui/QrScanner.tsx @@ -1,83 +1,79 @@ -import { useEffect, useRef, useState } from "react"; -import QrScanner from "qr-scanner"; -import { useTicketQrCodeValidate } from "../hooks/useOrderHook"; -import Button from "../../../../design-system/ui/Button"; +import { useEffect, useRef, useState } from 'react'; +import QrScanner from 'qr-scanner'; +import { useTicketQrCodeValidate } from '../hooks/useOrderHook'; +import Button from '../../../../design-system/ui/Button'; const QrScannerComponent = () => { - const videoRef = useRef(null); - const [isScanning, setIsScanning] = useState(false); - const qrScannerRef = useRef(null); + const videoRef = useRef(null); + const [isScanning, setIsScanning] = useState(false); + const qrScannerRef = useRef(null); - const { mutate: validateQr } = useTicketQrCodeValidate(); + const { mutate: validateQr } = useTicketQrCodeValidate(); - useEffect(() => { - if (!videoRef.current) return; + useEffect(() => { + if (!videoRef.current) return; - const scanner = new QrScanner( - videoRef.current, - (decoded) => { - const data = decoded.data; - setIsScanning(false); - scanner.stop(); - checkInApiCall(data); - }, - { - onDecodeError: (err) => { - console.warn("QR decode error:", err); - }, - highlightScanRegion: true, - maxScansPerSecond: 5, - } - ); - - qrScannerRef.current = scanner; + const scanner = new QrScanner( + videoRef.current, + decoded => { + const data = decoded.data; + setIsScanning(false); + scanner.stop(); + checkInApiCall(data); + }, + { + onDecodeError: err => { + console.warn('QR decode error:', err); + }, + highlightScanRegion: true, + maxScansPerSecond: 5, + } + ); - return () => { - scanner.destroy(); - qrScannerRef.current = null; - }; - }, []); + qrScannerRef.current = scanner; - const handleStartScan = async () => { - try { - await navigator.mediaDevices.getUserMedia({ video: true }); - qrScannerRef.current?.start(); - setIsScanning(true); - } catch (e) { - alert( - "카메라 접근이 차단되어 있어 스캔할 수 없습니다.\n시스템 설정 또는 브라우저 설정에서 권한을 허용해 주세요." - ); - } + return () => { + scanner.destroy(); + qrScannerRef.current = null; }; + }, []); - const checkInApiCall = async (qrData: string) => { - try { - const params = new URLSearchParams(qrData.replace(/-/g, '=')); - const orderIdStr = params.get('orderId'); - const sig = params.get('sig'); - const orderId = orderIdStr ? parseInt(orderIdStr, 10) : NaN; + const handleStartScan = async () => { + try { + await navigator.mediaDevices.getUserMedia({ video: true }); + qrScannerRef.current?.start(); + setIsScanning(true); + } catch { + alert( + '카메라 접근이 차단되어 있어 스캔할 수 없습니다.\n시스템 설정 또는 브라우저 설정에서 권한을 허용해 주세요.' + ); + } + }; - if (!isNaN(orderId) && sig) { - validateQr({ orderId, sig }); - } else { - alert("QR 코드 데이터 형식이 올바르지 않습니다."); - } - } catch { - alert("QR 코드 데이터를 파싱할 수 없습니다."); - } - }; + const checkInApiCall = async (qrData: string) => { + try { + const params = new URLSearchParams(qrData.replace(/-/g, '=')); + const orderIdStr = params.get('orderId'); + const sig = params.get('sig'); + const orderId = orderIdStr ? parseInt(orderIdStr, 10) : NaN; - return ( -
-
- ); + if (!isNaN(orderId) && sig) { + validateQr({ orderId, sig }); + } else { + alert('QR 코드 데이터 형식이 올바르지 않습니다.'); + } + } catch { + alert('QR 코드 데이터를 파싱할 수 없습니다.'); + } + }; + + return ( +
+
+ ); }; export default QrScannerComponent; From 937d2706bb0cdcb3ded3fd94c7747ef5bf63c747 Mon Sep 17 00:00:00 2001 From: Yejiin21 <101397075+Yejiin21@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:57:07 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refac:=20=EC=97=90=EB=9F=AC=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/ticket/hooks/useOrderHook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/ticket/hooks/useOrderHook.ts b/src/features/ticket/hooks/useOrderHook.ts index c65fcb0f..b59d4dc7 100644 --- a/src/features/ticket/hooks/useOrderHook.ts +++ b/src/features/ticket/hooks/useOrderHook.ts @@ -54,7 +54,7 @@ export const useTicketQrCodeValidate = () => { onSuccess: () => { alert('체크인 성공!'); }, - onError: (error: any) => { + onError: (error: { code?: string }) => { const errorCode = error?.code; if (errorCode === 'QR_CODE4001') { alert('이미 사용된 QR 코드입니다.');