fix(insta): SlateDetail JSON 객체 렌더 오류 + 카드 생성 시 자동 스크롤
(1) React error #31 fix: cover_copy/cta_copy는 객체({headline,body,accent_color}), body_copies는 배열 — 직접 {slate.cover_copy}로 렌더하면 에러. 필드별로 분해 렌더하고, 10페이지 전체 카피(커버 1 + 본문 8 + CTA 1)를 detail에 노출하도록 SlateDetail 확장. (2) UX: handleCreateSlate 시작 시 window.scrollTo(0, 0)로 상단 progress 배너 노출 보장. KeywordsPanel의 🎴 버튼도 부모 handleCreateSlate 위임으로 통합 — Trends/Cards 양쪽 어디서 눌러도 동일 흐름(배너 + 자동 미리보기). (3) KeywordsPanel의 자체 slatePoll 제거 — 상단 ic-slate-progress 배너로 일원화하여 중복 진행 표시 회피.
This commit is contained in:
@@ -337,6 +337,10 @@ export default function InstaCards() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSlateProgress({ keyword, status: 'starting', message: '카드 생성 시작...' });
|
setSlateProgress({ keyword, status: 'starting', message: '카드 생성 시작...' });
|
||||||
|
// 상단 progress 배너가 보이도록 스크롤 (Trends/Cards 어느 탭의 어느 위치에서 눌렀든)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { task_id } = await createInstaSlate({ keyword, category, keyword_id });
|
const { task_id } = await createInstaSlate({ keyword, category, keyword_id });
|
||||||
let st = null;
|
let st = null;
|
||||||
@@ -432,7 +436,7 @@ export default function InstaCards() {
|
|||||||
<div>
|
<div>
|
||||||
<TriggerPanel />
|
<TriggerPanel />
|
||||||
<div style={{ height: 16 }} />
|
<div style={{ height: 16 }} />
|
||||||
<KeywordsPanel onCreateSlate={() => setSelectedSlateId(null)} />
|
<KeywordsPanel onCreateSlate={handleCreateSlate} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 오른쪽: 슬레이트 목록 + 상세 */}
|
{/* 오른쪽: 슬레이트 목록 + 상세 */}
|
||||||
@@ -522,10 +526,6 @@ function KeywordsPanel({ onCreateSlate }) {
|
|||||||
const [category, setCategory] = useState('전체');
|
const [category, setCategory] = useState('전체');
|
||||||
const [keywords, setKeywords] = useState([]);
|
const [keywords, setKeywords] = useState([]);
|
||||||
const [creating, setCreating] = useState(null); // keyword_id being created
|
const [creating, setCreating] = useState(null); // keyword_id being created
|
||||||
const slatePoll = usePollTask((t) => {
|
|
||||||
if (t.status === 'succeeded') onCreateSlate?.();
|
|
||||||
setCreating(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
const cat = category === '전체' ? undefined : category;
|
const cat = category === '전체' ? undefined : category;
|
||||||
@@ -534,18 +534,17 @@ function KeywordsPanel({ onCreateSlate }) {
|
|||||||
|
|
||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
// 부모(InstaCards)의 handleCreateSlate에 위임 — progress 배너 + 스크롤 + 자동 미리보기 공통화
|
||||||
async function handleCreate(kw) {
|
async function handleCreate(kw) {
|
||||||
if (creating) return;
|
if (creating) return;
|
||||||
setCreating(kw.id);
|
setCreating(kw.id);
|
||||||
try {
|
try {
|
||||||
const res = await createInstaSlate({
|
await onCreateSlate?.({
|
||||||
keyword: kw.keyword,
|
keyword: kw.keyword,
|
||||||
category: kw.category,
|
category: kw.category,
|
||||||
keyword_id: kw.id,
|
keyword_id: kw.id,
|
||||||
});
|
});
|
||||||
slatePoll.start(res.task_id);
|
} finally {
|
||||||
} catch (e) {
|
|
||||||
alert('카드 생성 실패: ' + e.message);
|
|
||||||
setCreating(null);
|
setCreating(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +566,7 @@ function KeywordsPanel({ onCreateSlate }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{slatePoll.task && <TaskStatusBox task={slatePoll.task} />}
|
{/* progress 표시는 상단 ic-slate-progress 배너에서 일괄 처리 */}
|
||||||
|
|
||||||
{keywords.length === 0 ? (
|
{keywords.length === 0 ? (
|
||||||
<div className="ic-empty">키워드가 없습니다. 키워드 추출을 실행하세요.</div>
|
<div className="ic-empty">키워드가 없습니다. 키워드 추출을 실행하세요.</div>
|
||||||
@@ -754,11 +753,66 @@ function SlateDetail({ slate, onDelete, onRender }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 커버 카피 / 바디 카피 */}
|
{/* 커버 카피 (1/10) */}
|
||||||
{slate.cover_copy && (
|
{slate.cover_copy && typeof slate.cover_copy === 'object' && (
|
||||||
<div className="ic-caption-box">
|
<div className="ic-caption-box">
|
||||||
<div className="ic-caption-box__label">커버 카피</div>
|
<div className="ic-caption-box__label">🎯 커버 (1/10)</div>
|
||||||
<div className="ic-caption-text">{slate.cover_copy}</div>
|
<div className="ic-caption-text">
|
||||||
|
<strong>{slate.cover_copy.headline}</strong>
|
||||||
|
{slate.cover_copy.body && (
|
||||||
|
<div style={{ marginTop: 6, opacity: 0.85, whiteSpace: 'pre-wrap' }}>
|
||||||
|
{slate.cover_copy.body}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{slate.cover_copy.accent_color && (
|
||||||
|
<div style={{ marginTop: 6, fontSize: '0.72rem', opacity: 0.5 }}>
|
||||||
|
accent: <code>{slate.cover_copy.accent_color}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 본문 카피 8장 (2~9/10) */}
|
||||||
|
{Array.isArray(slate.body_copies) && slate.body_copies.length > 0 && (
|
||||||
|
<div className="ic-caption-box">
|
||||||
|
<div className="ic-caption-box__label">📝 본문 8장 (2~9/10)</div>
|
||||||
|
{slate.body_copies.map((b, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
borderTop: i > 0 ? '1px solid rgba(255,255,255,0.06)' : 'none',
|
||||||
|
padding: '10px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>{i + 2}. {b?.headline || ''}</strong>
|
||||||
|
{b?.body && (
|
||||||
|
<div style={{ marginTop: 4, opacity: 0.85, whiteSpace: 'pre-wrap' }}>
|
||||||
|
{b.body}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CTA 카피 (10/10) */}
|
||||||
|
{slate.cta_copy && typeof slate.cta_copy === 'object' && (
|
||||||
|
<div className="ic-caption-box">
|
||||||
|
<div className="ic-caption-box__label">📣 마무리 (10/10)</div>
|
||||||
|
<div className="ic-caption-text">
|
||||||
|
<strong>{slate.cta_copy.headline}</strong>
|
||||||
|
{slate.cta_copy.body && (
|
||||||
|
<div style={{ marginTop: 6, opacity: 0.85, whiteSpace: 'pre-wrap' }}>
|
||||||
|
{slate.cta_copy.body}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{slate.cta_copy.cta && (
|
||||||
|
<div style={{ marginTop: 8, color: '#ec4899', fontWeight: 700 }}>
|
||||||
|
CTA: {slate.cta_copy.cta}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user