| 
					
				 | 
			
			
				@@ -14,20 +14,30 @@ export function clearApiToken() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 export function apiRequest(method, url, payload = null, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     expectJson = true, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     useForm = false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    timeout = 10000, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    requireAuth = true  // 👈 기본은 true, 로그인 등은 false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    requireAuth = true, // 인증이 필요한지 여부 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    timeout = 10000 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } = {}) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const token = getApiToken(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (requireAuth && !token) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return Promise.reject("토큰 없음"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Promise.reject({ status: 401, message: "토큰 없음" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const normalizedMethod = method.toUpperCase(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const headers = {}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    // 인증이 필요한 경우에만 Authorization 헤더 추가 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // 인증 헤더 조건부 추가 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (requireAuth && token) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         headers["Authorization"] = `Bearer ${token}`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // 컨텐츠 타입 지정 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (useForm) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        headers["Content-Type"] = "application/x-www-form-urlencoded"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (normalizedMethod !== "GET" && normalizedMethod !== "DELETE") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        headers["Content-Type"] = "application/json"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // CSRF 토큰 자동 추가 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const csrfToken = document.querySelector("meta[name='_csrf']")?.content; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.content; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -35,7 +45,6 @@ export function apiRequest(method, url, payload = null, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         headers[csrfHeader] = csrfToken; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    const normalizedMethod = method.toUpperCase(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let fullUrl = url; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let body = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -46,13 +55,9 @@ export function apiRequest(method, url, payload = null, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // POST/PUT = JSON 또는 x-www-form-urlencoded 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     else if (payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if (useForm) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            headers["Content-Type"] = "application/x-www-form-urlencoded"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            body = new URLSearchParams(payload).toString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            headers["Content-Type"] = "application/json"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            body = JSON.stringify(payload); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        body = useForm 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ? new URLSearchParams(payload).toString() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            : JSON.stringify(payload); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // 타임아웃 처리 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -64,8 +69,8 @@ export function apiRequest(method, url, payload = null, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         headers, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         body, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         signal: controller.signal 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    }).then(res => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        res.text().then(raw => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        .then(res => res.text().then(raw => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             clearTimeout(timeoutId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if (!res.ok) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return Promise.reject({ 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -80,16 +85,52 @@ export function apiRequest(method, url, payload = null, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } catch { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return raw; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    ).catch(err => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        clearTimeout(timeoutId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if (err.name === "AbortError") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            return Promise.reject({ status: 408, message: "요청 시간이 초과되었습니다." }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return Promise.reject({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            status: err.status || 500, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            message: err.message || "알 수 없는 오류", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            body: err.body || null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        })) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        .catch(err => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            clearTimeout(timeoutId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (err.name === "AbortError") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return Promise.reject({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    status: 408, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    message: "요청 시간이 초과되었습니다." }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return Promise.reject({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                status: err?.status || 500, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message: err?.message || "알 수 없는 오류", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                body: err?.body || null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+export function handleApiError(error) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const { status, message, body } = error; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (error instanceof TypeError) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        alert("네트워크 연결 오류: 인터넷 상태를 확인해주세요."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    switch (status) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        case 401: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert("로그인이 필요합니다."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            location.href = "/login"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        case 403: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert("접근 권한이 없습니다."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        case 404: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert("요청하신 정보를 찾을 수 없습니다."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        case 408: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert("요청 시간이 초과되었습니다. 다시 시도해주세요."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        case 500: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert("서버 오류 발생. 관리자에게 문의해주세요."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            console.error("서버 응답:", body); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            alert(`요청 실패: ${message || "알 수 없는 오류입니다."}`); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            console.error("에러 상세:", error); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |