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;
|
||||
}
|
||||
setSlateProgress({ keyword, status: 'starting', message: '카드 생성 시작...' });
|
||||
// 상단 progress 배너가 보이도록 스크롤 (Trends/Cards 어느 탭의 어느 위치에서 눌렀든)
|
||||
if (typeof window !== 'undefined') {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
try {
|
||||
const { task_id } = await createInstaSlate({ keyword, category, keyword_id });
|
||||
let st = null;
|
||||
@@ -432,7 +436,7 @@ export default function InstaCards() {
|
||||
<div>
|
||||
<TriggerPanel />
|
||||
<div style={{ height: 16 }} />
|
||||
<KeywordsPanel onCreateSlate={() => setSelectedSlateId(null)} />
|
||||
<KeywordsPanel onCreateSlate={handleCreateSlate} />
|
||||
</div>
|
||||
|
||||
{/* 오른쪽: 슬레이트 목록 + 상세 */}
|
||||
@@ -522,10 +526,6 @@ function KeywordsPanel({ onCreateSlate }) {
|
||||
const [category, setCategory] = useState('전체');
|
||||
const [keywords, setKeywords] = useState([]);
|
||||
const [creating, setCreating] = useState(null); // keyword_id being created
|
||||
const slatePoll = usePollTask((t) => {
|
||||
if (t.status === 'succeeded') onCreateSlate?.();
|
||||
setCreating(null);
|
||||
});
|
||||
|
||||
const load = useCallback(() => {
|
||||
const cat = category === '전체' ? undefined : category;
|
||||
@@ -534,18 +534,17 @@ function KeywordsPanel({ onCreateSlate }) {
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
// 부모(InstaCards)의 handleCreateSlate에 위임 — progress 배너 + 스크롤 + 자동 미리보기 공통화
|
||||
async function handleCreate(kw) {
|
||||
if (creating) return;
|
||||
setCreating(kw.id);
|
||||
try {
|
||||
const res = await createInstaSlate({
|
||||
await onCreateSlate?.({
|
||||
keyword: kw.keyword,
|
||||
category: kw.category,
|
||||
keyword_id: kw.id,
|
||||
});
|
||||
slatePoll.start(res.task_id);
|
||||
} catch (e) {
|
||||
alert('카드 생성 실패: ' + e.message);
|
||||
} finally {
|
||||
setCreating(null);
|
||||
}
|
||||
}
|
||||
@@ -567,7 +566,7 @@ function KeywordsPanel({ onCreateSlate }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{slatePoll.task && <TaskStatusBox task={slatePoll.task} />}
|
||||
{/* progress 표시는 상단 ic-slate-progress 배너에서 일괄 처리 */}
|
||||
|
||||
{keywords.length === 0 ? (
|
||||
<div className="ic-empty">키워드가 없습니다. 키워드 추출을 실행하세요.</div>
|
||||
@@ -754,11 +753,66 @@ function SlateDetail({ slate, onDelete, onRender }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 커버 카피 / 바디 카피 */}
|
||||
{slate.cover_copy && (
|
||||
{/* 커버 카피 (1/10) */}
|
||||
{slate.cover_copy && typeof slate.cover_copy === 'object' && (
|
||||
<div className="ic-caption-box">
|
||||
<div className="ic-caption-box__label">커버 카피</div>
|
||||
<div className="ic-caption-text">{slate.cover_copy}</div>
|
||||
<div className="ic-caption-box__label">🎯 커버 (1/10)</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>
|
||||
|
||||
Reference in New Issue
Block a user