diff --git a/src/components/dashboard/platform/AllPlatformView.tsx b/src/components/dashboard/platform/AllPlatformView.tsx index 5bb66c88..45449951 100644 --- a/src/components/dashboard/platform/AllPlatformView.tsx +++ b/src/components/dashboard/platform/AllPlatformView.tsx @@ -103,8 +103,17 @@ export default function AllPlatformView({ isLoading }: IAllPlatformViewProps) { {/* 실시간 트래픽 변화 */} - - {isLoading ? : null} + + {isLoading ? ( + + ) : ( +
+ Coming Soon +

+ 실시간 클릭 트래픽 모니터링 기능을 준비하고 있습니다. +

+
+ )}
{/* 개별 플랫폼 상세 */} diff --git a/src/components/dashboard/platform/SinglePlatformView.tsx b/src/components/dashboard/platform/SinglePlatformView.tsx index 2cd89011..fc498c16 100644 --- a/src/components/dashboard/platform/SinglePlatformView.tsx +++ b/src/components/dashboard/platform/SinglePlatformView.tsx @@ -171,9 +171,14 @@ export default function SinglePlatformView({
-
+
+ Coming Soon +

+ 실시간 클릭 트래픽 모니터링 기능을 준비하고 있습니다. +

+
{mainNav.map((item) => { diff --git a/src/constants/sidebarNav.ts b/src/constants/sidebarNav.ts index 0a36fe91..68a46a62 100644 --- a/src/constants/sidebarNav.ts +++ b/src/constants/sidebarNav.ts @@ -61,12 +61,6 @@ export const mainNav: INavItem[] = [ }, ], }, - { - id: "settings", - label: "설정", - icon: SettingsIcon, - path: "/setting", - }, ]; export const footerNav: INavItem[] = [ diff --git a/src/hooks/dashboard/useClickStream.ts b/src/hooks/dashboard/useClickStream.ts index a63bb8ea..b3ddd383 100644 --- a/src/hooks/dashboard/useClickStream.ts +++ b/src/hooks/dashboard/useClickStream.ts @@ -7,6 +7,7 @@ import useAuthStore from "@/store/useAuthStore"; import useWorkspaceStore from "@/store/useWorkspaceStore"; const MAX_RETRIES = 3; +const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? "").replace(/\/$/, ""); export function useClickStream(mode: "real" | "dummy" = "dummy") { // TODO: 테스트용 - 더미 데이터가 orgId 1에만 있어서 임시 고정, 테스트 후 원래대로 복구할 예정 @@ -26,40 +27,48 @@ export function useClickStream(mode: "real" | "dummy" = "dummy") { const controller = new AbortController(); retryCountRef.current = 0; - fetchEventSource(`/api/dashboard/${orgId}/clicks/stream?mode=${mode}`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - signal: controller.signal, - onopen: async (response) => { - if (response.ok) { + fetchEventSource( + `${BASE_URL}/api/dashboard/${orgId}/clicks/stream?mode=${mode}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + signal: controller.signal, + onopen: async (response) => { + const contentType = response.headers.get("content-type") ?? ""; + const isSse = contentType.includes("text/event-stream"); + if (!response.ok || !isSse) { + throw new Error( + `Invalid SSE response: status=${response.status}, content-type=${contentType}`, + ); + } retryCountRef.current = 0; setIsError(false); - } - }, - onmessage(event) { - if (event.event === "org-click-update") { - try { - const wrapper = JSON.parse(event.data); - const parsed: IClickStreamItem = wrapper.data ?? wrapper; - setData(parsed); - if (parsed.suspectDetail) { - setSuspectDetail(parsed.suspectDetail); + }, + onmessage(event) { + if (event.event === "org-click-update") { + try { + const wrapper = JSON.parse(event.data); + const parsed: IClickStreamItem = wrapper.data ?? wrapper; + setData(parsed); + if (parsed.suspectDetail) { + setSuspectDetail(parsed.suspectDetail); + } + } catch { + // 파싱 실패 시 해당 이벤트 무시하고 스트림 유지 } - } catch { - // 파싱 실패 시 해당 이벤트 무시하고 스트림 유지 } - } - }, - onerror(err) { - retryCountRef.current += 1; - if (retryCountRef.current >= MAX_RETRIES) { - setIsError(true); - throw err; // 재시도 중단 - } - // MAX_RETRIES 미만이면 기본 재시도 동작 유지 + }, + onerror(err) { + retryCountRef.current += 1; + if (retryCountRef.current >= MAX_RETRIES) { + setIsError(true); + throw err; // 재시도 중단 + } + // MAX_RETRIES 미만이면 기본 재시도 동작 유지 + }, }, - }); + ); return () => controller.abort(); }, [orgId, accessToken, mode]);