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); + }); +});