diff --git a/src/components/admin/TemplatesAdmin.tsx b/src/components/admin/TemplatesAdmin.tsx index 4ba2b62..bc98133 100644 --- a/src/components/admin/TemplatesAdmin.tsx +++ b/src/components/admin/TemplatesAdmin.tsx @@ -103,15 +103,35 @@ export function TemplatesAdmin() { setSavingProj(null); }; - // Attach an uploaded After Effects file (+ composition) to a project. + // Attach an uploaded After Effects file (+ composition) to a project, then PROMOTE + // it into the render bucket so render nodes can actually use it. Two steps: + // 1. PATCH /projects/{id}/aep — record aep_file_url + comp on the content project + // 2. POST /template-bundles/{id} — copy the uploaded .aep/.zip into + // templates/{id}/(bundle.zip|template.aep) where the node-agent claim looks. + // Without step 2 the upload is only a reference and renders fail / fall back to mock. const attachAep = async (p: Proj, url: string) => { setSavingProj(p.id); setError(null); const res = await fetch(api(`projects/${p.id}/aep`), { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ aep_file_url: url, render_aep_comp: p.render_aep_comp || "flatrender" }), }); - if (res.ok) { updateProj(p.id, { aep_file_url: url }); } - else { const d = await res.json().catch(() => null); setError(d?.error ?? "اتصال فایل AE ناموفق بود"); } + if (!res.ok) { + const d = await res.json().catch(() => null); + setError(d?.error ?? "اتصال فایل AE ناموفق بود"); + setSavingProj(null); + return; + } + updateProj(p.id, { aep_file_url: url }); + + // Promote to the render templates bucket (makes it renderable on nodes). + const promote = await fetch(api(`template-bundles/${p.id}`), { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ source_url: url }), + }); + if (!promote.ok) { + const d = await promote.json().catch(() => null); + setError(d?.message ?? d?.error ?? "آماده‌سازی فایل برای رندر ناموفق بود (فایل ذخیره شد ولی هنوز قابل رندر نیست)."); + } setSavingProj(null); };