// === 수동 상품 등록 === // ISVM 없이 사용자가 직접 정보 입력 → 네이버 스토어에 등록. // 기존 naverRegisterFromTemplate 재사용 (카테고리/원산지/인증 메타는 매장 라이브 상품에서 자동 참조). function ManualRegisterScreen({ products }) { const isConfigured = window.API.naverConfigured(); // 폼 state const [name, setName] = useState(""); const [salePrice, setSalePrice] = useState(""); const [stock, setStock] = useState(""); const [sellerCode, setSellerCode] = useState(""); const [leafCategoryId, setLeafCategoryId] = useState(""); const [descText, setDescText] = useState(""); // 이미지: [{ file, preview, naverUrl? }] const [images, setImages] = useState([]); const [mainIdx, setMainIdx] = useState(0); // 등록 진행 const [registering, setRegistering] = useState(false); const [progress, setProgress] = useState(null); // { done, total, label } const [result, setResult] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); // template 자동 선택 (카테고리/원산지/인증 메타용) const templateProductId = products.find(p => p.originProductNo)?.originProductNo; const fileInputRef = useRef(null); const handleFiles = (fileList) => { if (!fileList || fileList.length === 0) return; const next = []; Array.from(fileList).forEach(file => { if (!file.type.startsWith("image/")) return; next.push({ id: Date.now() + "-" + Math.random().toString(36).slice(2, 8), file, preview: URL.createObjectURL(file), }); }); setImages(prev => [...prev, ...next].slice(0, 10)); // 최대 10장 (대표 1 + 추가 9) }; const removeImage = (id) => { setImages(prev => { const next = prev.filter(it => it.id !== id); const removedIdx = prev.findIndex(it => it.id === id); // 메인 이미지 인덱스 조정 if (removedIdx === mainIdx) setMainIdx(0); else if (removedIdx < mainIdx) setMainIdx(i => Math.max(0, i - 1)); return next; }); }; const setAsMain = (idx) => setMainIdx(idx); const resetForm = () => { images.forEach(it => URL.revokeObjectURL(it.preview)); setName(""); setSalePrice(""); setStock(""); setSellerCode(""); setLeafCategoryId(""); setDescText(""); setImages([]); setMainIdx(0); setProgress(null); setResult(null); }; const validate = () => { if (!name.trim()) return "상품명을 입력하세요"; if (!parseInt(salePrice)) return "판매가를 입력하세요"; if (parseInt(salePrice) % 10 !== 0) return "판매가는 10원 단위여야 합니다"; if (!parseInt(stock) && parseInt(stock) !== 0) return "재고를 입력하세요 (0 이상)"; if (images.length === 0) return "이미지를 1장 이상 추가하세요"; if (!templateProductId) return "매장에 라이브 상품이 1개 이상 있어야 등록 가능 (카테고리/원산지 참조용)"; return null; }; const doRegister = async () => { const err = validate(); if (err) { toast(err); return; } setConfirmOpen(false); setRegistering(true); setResult(null); try { // 메인 이미지를 첫 번째로 정렬 const ordered = [images[mainIdx], ...images.filter((_, i) => i !== mainIdx)]; // 순차 업로드 const uploadedUrls = []; setProgress({ done: 0, total: ordered.length, label: "이미지 업로드 중" }); for (let i = 0; i < ordered.length; i++) { try { const url = await window.API.naverUploadImageFile(ordered[i].file); uploadedUrls.push(url); } catch (e) { console.warn("[manual upload " + (i + 1) + "/" + ordered.length + " 실패]", e.message); } setProgress({ done: i + 1, total: ordered.length, label: "이미지 업로드 중" }); } if (uploadedUrls.length === 0) throw new Error("모든 이미지 업로드 실패"); // 상세설명 HTML — 사용자 입력 텍스트 + 모든 이미지를 SmartEditor 형식으로 let detailHtml = ""; const cleanedDesc = (descText || "").replace(/[<>]/g, "").trim(); if (cleanedDesc) { const escaped = cleanedDesc.split("\n").map(line => `

${line || " "}

`).join(""); detailHtml = `
${escaped}
`; } // 이미지 HTML 추가 const imgHtml = window.API.buildSmartEditorImagesHtml(uploadedUrls); if (imgHtml) { // 두 HTML 합치기 — 텍스트 + 이미지 컴포넌트 if (detailHtml) { // 두 viewer를 합쳐서 하나로 (네이버는 single viewer 안에 component들이 있는 구조 선호) detailHtml = imgHtml.replace("", cleanedDesc ? `
${cleanedDesc.split("\n").map(line => `

${line || " "}

`).join("")}
` : "" ); } else { detailHtml = imgHtml; } } // 등록 setProgress({ done: 0, total: 1, label: "네이버에 등록 중" }); const overrides = { name: name.trim(), salePrice: parseInt(salePrice) || 0, stockQuantity: parseInt(stock) || 0, imageUrl: uploadedUrls[0], optionalImageUrls: uploadedUrls.slice(1, 10), detailContent: detailHtml || undefined, }; if (sellerCode.trim()) overrides.sellerCode = sellerCode.trim(); if (leafCategoryId.trim()) overrides.leafCategoryId = leafCategoryId.trim(); const r = await window.API.naverRegisterFromTemplate(parseInt(templateProductId), overrides); setProgress(null); setResult({ ok: true, response: r }); toast(`'${name.slice(0, 24)}…' 네이버에 등록되었습니다`); } catch (e) { console.error("[manual register]", e); setResult({ ok: false, error: e.message }); toast("등록 실패: " + e.message); setProgress(null); } finally { setRegistering(false); } }; const onDrop = (e) => { e.preventDefault(); if (e.dataTransfer?.files) handleFiles(e.dataTransfer.files); }; return (

수동 상품 등록

ISVM 없이 직접 정보 입력해서 네이버 스마트스토어에 등록합니다 · 카테고리/원산지/인증 메타는 매장의 라이브 상품에서 자동 참조 · 배송/A&S는 API 설정의 기본값 사용

{result?.ok && ( )}
{!isConfigured && (
API 자격증명이 설정되지 않았습니다. API 설정 메뉴에서 입력하세요.
)} {!templateProductId && isConfigured && (
매장에 라이브 상품이 0개라 카테고리 참조 불가. 먼저 1개 이상 등록되어야 합니다.
)}
{/* 왼쪽: 기본 정보 */}
1. 기본 정보
{templateProductId && ( 기준상품 #{templateProductId} )}
setName(e.target.value)} />
금지 문자(\\ * ? " < >)는 자동 정리됩니다 · 글자수: {name.length}
setSalePrice(e.target.value)} />
10원 단위
setStock(e.target.value)} />
setSellerCode(e.target.value)} /> setLeafCategoryId(e.target.value)} />
비우면 기준상품 카테고리 사용