diff --git a/src/pages/blog-marketing/BlogMarketing.css b/src/pages/blog-marketing/BlogMarketing.css index e597b18..4ac6ae8 100644 --- a/src/pages/blog-marketing/BlogMarketing.css +++ b/src/pages/blog-marketing/BlogMarketing.css @@ -125,6 +125,18 @@ .bm-empty { text-align: center; padding: 48px 20px; color: rgba(255,255,255,.25); font-size: 0.85rem; } /* ── 모바일 ───────────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .bm-tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .bm-tabs > * { + flex-shrink: 0; + white-space: nowrap; + } +} + @media (max-width: 480px) { .bm { padding: 16px 10px 60px; } .bm-header h1 { font-size: 1.2rem; } diff --git a/src/pages/blog-marketing/BlogMarketing.jsx b/src/pages/blog-marketing/BlogMarketing.jsx index f602d1c..131661c 100644 --- a/src/pages/blog-marketing/BlogMarketing.jsx +++ b/src/pages/blog-marketing/BlogMarketing.jsx @@ -1,4 +1,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { useIsMobile } from '../../hooks/useIsMobile'; +import PullToRefresh from '../../components/PullToRefresh'; +import FAB from '../../components/FAB'; import { getBlogMarketingStatus, startResearch, @@ -81,13 +84,18 @@ function usePollTask(onDone) { /* ══════════════════════════════════════════════════════════════════════════ */ export default function BlogMarketing() { + const isMobile = useIsMobile(); const [tab, setTab] = useState('dashboard'); const [status, setStatus] = useState(null); - useEffect(() => { - getBlogMarketingStatus().then(setStatus).catch(() => {}); + const loadStatus = useCallback(() => { + return getBlogMarketingStatus().then(setStatus).catch(() => {}); }, []); + useEffect(() => { + loadStatus(); + }, [loadStatus]); + const tabs = [ { id: 'dashboard', label: 'Dashboard' }, { id: 'research', label: 'Research' }, @@ -96,6 +104,7 @@ export default function BlogMarketing() { ]; return ( +

Blog Lab

@@ -127,7 +136,12 @@ export default function BlogMarketing() { {tab === 'research' && { setTab('write'); }} />} {tab === 'write' && } {tab === 'posts' && } + + {tab === 'research' && ( + setTab('research')} label="키워드 분석" /> + )}
+
); } diff --git a/src/pages/blog/Blog.css b/src/pages/blog/Blog.css index 119cd59..7978bba 100644 --- a/src/pages/blog/Blog.css +++ b/src/pages/blog/Blog.css @@ -758,4 +758,19 @@ align-self: stretch; text-align: center; } + + /* 태그/카테고리 필터 가로 스크롤 */ + .blog-categories, + .blog-category-list { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + flex-wrap: nowrap; + gap: 8px; + } + + .blog-categories > *, + .blog-category-list > * { + flex-shrink: 0; + } } diff --git a/src/pages/blog/Blog.jsx b/src/pages/blog/Blog.jsx index a23698d..a82bcb4 100644 --- a/src/pages/blog/Blog.jsx +++ b/src/pages/blog/Blog.jsx @@ -6,6 +6,9 @@ import { updateBlogPost, deleteBlogPost, } from '../../api'; +import { useIsMobile } from '../../hooks/useIsMobile'; +import PullToRefresh from '../../components/PullToRefresh'; +import FAB from '../../components/FAB'; import './Blog.css'; // ── 마크다운 렌더러 ────────────────────────────────────────────────────────── @@ -353,15 +356,15 @@ const BlogEditor = ({ post, onSave, onClose }) => { // ── 메인 Blog 컴포넌트 ─────────────────────────────────────────────────────── const Blog = () => { + const isMobile = useIsMobile(); const staticPosts = useMemo(() => getBlogPosts(), []); const [apiPosts, setApiPosts] = useState([]); const [apiError, setApiError] = useState(false); const [editorPost, setEditorPost] = useState(null); // null=닫힘, {}=새글, post=수정 const [isEditorOpen, setIsEditorOpen] = useState(false); - // API 글 불러오기 - useEffect(() => { - getBlogPostsApi() + const fetchPosts = useCallback(() => { + return getBlogPostsApi() .then((data) => { const posts = Array.isArray(data) ? data : (data?.posts ?? []); setApiPosts(posts.map((p) => ({ ...p, slug: `api-${p.id}` }))); @@ -369,6 +372,11 @@ const Blog = () => { .catch(() => setApiError(true)); }, []); + // API 글 불러오기 + useEffect(() => { + fetchPosts(); + }, [fetchPosts]); + // 정적 + API 글 병합 (API 글이 앞에 표시) const allPosts = useMemo(() => { const combined = [...apiPosts, ...staticPosts]; @@ -450,6 +458,7 @@ const Blog = () => { const closeEditor = useCallback(() => { setIsEditorOpen(false); setEditorPost(null); }, []); return ( +
@@ -651,7 +660,10 @@ const Blog = () => { onClose={closeEditor} /> )} + +
+ ); };