diff --git a/src/pages/insta/InstaCards.jsx b/src/pages/insta/InstaCards.jsx
index 244e7bc..f60d3e4 100644
--- a/src/pages/insta/InstaCards.jsx
+++ b/src/pages/insta/InstaCards.jsx
@@ -21,81 +21,10 @@ import {
putInstaPreferences,
} from '../../api';
import './InstaCards.css';
-
-/* ────────────────────── 유틸 ────────────────────── */
-function fmtDate(iso) {
- if (!iso) return '';
- return new Date(iso).toLocaleDateString('ko-KR', {
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- });
-}
-
-function StatusBadge({ status }) {
- return (
-
- {status || 'draft'}
-
- );
-}
-
-/* ────────────────────── 폴링 훅 ────────────────────── */
-function usePollTask(onDone) {
- const [taskId, setTaskId] = useState(null);
- const [task, setTask] = useState(null);
- const timer = useRef(null);
-
- useEffect(() => {
- if (!taskId) return;
- let cancelled = false;
- const poll = async () => {
- try {
- const t = await getInstaTask(taskId);
- if (cancelled) return;
- setTask(t);
- if (t.status === 'succeeded' || t.status === 'failed') {
- setTaskId(null);
- onDone?.(t);
- } else {
- timer.current = setTimeout(poll, 3000);
- }
- } catch {
- if (!cancelled) timer.current = setTimeout(poll, 3000);
- }
- };
- poll();
- return () => {
- cancelled = true;
- clearTimeout(timer.current);
- };
- }, [taskId]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return {
- taskId,
- task,
- start: setTaskId,
- clear: () => { setTaskId(null); setTask(null); },
- };
-}
-
-/* ────────────────────── TaskStatusBox ────────────────────── */
-function TaskStatusBox({ task }) {
- if (!task) return null;
- const pct = task.progress != null ? task.progress : (task.status === 'succeeded' ? 100 : 0);
- return (
-
-
- {task.status === 'succeeded' ? '완료' : task.status === 'failed' ? '실패' : '진행 중'}
-
-
{task.message || task.error || ''}
-
-
- );
-}
+import { fmtDate } from './instaUtils';
+import usePollTask from './hooks/usePollTask';
+import StatusBadge from './components/StatusBadge';
+import TaskStatusBox from './components/TaskStatusBox';
/* ══════════════════════ Trends 탭 패널 1: AccountFocusPanel ══════════════ */
function AccountFocusPanel() {
diff --git a/src/pages/insta/components/StatusBadge.jsx b/src/pages/insta/components/StatusBadge.jsx
new file mode 100644
index 0000000..bbedab0
--- /dev/null
+++ b/src/pages/insta/components/StatusBadge.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function StatusBadge({ status }) {
+ return (
+
+ {status || 'draft'}
+
+ );
+}
+
+export default StatusBadge;
diff --git a/src/pages/insta/components/TaskStatusBox.jsx b/src/pages/insta/components/TaskStatusBox.jsx
new file mode 100644
index 0000000..64a63f6
--- /dev/null
+++ b/src/pages/insta/components/TaskStatusBox.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+/* ────────────────────── TaskStatusBox ────────────────────── */
+function TaskStatusBox({ task }) {
+ if (!task) return null;
+ const pct = task.progress != null ? task.progress : (task.status === 'succeeded' ? 100 : 0);
+ return (
+
+
+ {task.status === 'succeeded' ? '완료' : task.status === 'failed' ? '실패' : '진행 중'}
+
+
{task.message || task.error || ''}
+
+
+ );
+}
+
+export default TaskStatusBox;
diff --git a/src/pages/insta/hooks/usePollTask.js b/src/pages/insta/hooks/usePollTask.js
new file mode 100644
index 0000000..e0391d7
--- /dev/null
+++ b/src/pages/insta/hooks/usePollTask.js
@@ -0,0 +1,41 @@
+import { useState, useEffect, useRef } from 'react';
+import { getInstaTask } from '../../../api';
+
+/* ────────────────────── 폴링 훅 ────────────────────── */
+export default function usePollTask(onDone) {
+ const [taskId, setTaskId] = useState(null);
+ const [task, setTask] = useState(null);
+ const timer = useRef(null);
+
+ useEffect(() => {
+ if (!taskId) return;
+ let cancelled = false;
+ const poll = async () => {
+ try {
+ const t = await getInstaTask(taskId);
+ if (cancelled) return;
+ setTask(t);
+ if (t.status === 'succeeded' || t.status === 'failed') {
+ setTaskId(null);
+ onDone?.(t);
+ } else {
+ timer.current = setTimeout(poll, 3000);
+ }
+ } catch {
+ if (!cancelled) timer.current = setTimeout(poll, 3000);
+ }
+ };
+ poll();
+ return () => {
+ cancelled = true;
+ clearTimeout(timer.current);
+ };
+ }, [taskId]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return {
+ taskId,
+ task,
+ start: setTaskId,
+ clear: () => { setTaskId(null); setTask(null); },
+ };
+}
diff --git a/src/pages/insta/instaUtils.js b/src/pages/insta/instaUtils.js
new file mode 100644
index 0000000..201bb9f
--- /dev/null
+++ b/src/pages/insta/instaUtils.js
@@ -0,0 +1,10 @@
+// src/pages/insta/instaUtils.js
+export function fmtDate(iso) {
+ if (!iso) return '';
+ return new Date(iso).toLocaleDateString('ko-KR', {
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+}
diff --git a/src/pages/insta/instaUtils.test.js b/src/pages/insta/instaUtils.test.js
new file mode 100644
index 0000000..2ae6bb0
--- /dev/null
+++ b/src/pages/insta/instaUtils.test.js
@@ -0,0 +1,15 @@
+import { describe, it, expect } from 'vitest';
+import { fmtDate } from './instaUtils';
+
+describe('instaUtils.fmtDate', () => {
+ it('returns empty string for falsy input', () => {
+ expect(fmtDate('')).toBe('');
+ expect(fmtDate(null)).toBe('');
+ expect(fmtDate(undefined)).toBe('');
+ });
+ it('returns a non-empty string for a valid ISO date (locale/tz-dependent format)', () => {
+ const out = fmtDate('2026-07-10T09:30:00Z');
+ expect(typeof out).toBe('string');
+ expect(out.length).toBeGreaterThan(0);
+ });
+});