m.raw_variables && m.raw_variables.invoice_number ? el("span", { class: "badge" }, esc(m.raw_variables.invoice_number)) : null, m.raw_variables && m.raw_variables.amount != null ? el("span", { class: "badge" }, fmtMoney(m.raw_variables.amount)) : null, m.raw_variables && m.raw_variables.shipping != null ? el("span", { class: "badge" }, "ship " + fmtMoney(m.raw_variables.shipping)) : null), m.warnings && m.warnings.length ? el("div", { class: "notice warn", html: `⚠
${m.warnings.map(w => "• " + esc(w)).join("
")}
` }) : null, el("iframe", { class: "preview-frame", srcdoc: m.body_html })))) : el("p", { class: "muted" }, "No sample messages."), d.plan_summary ? el("p", { class: "muted small mt-2" }, "Plan summary: " + esc(JSON.stringify(d.plan_summary))) : null); openModal(`Preview campaign #${cid}`, node, { xw: true, buttons: [ { label: "Close", kind: "secondary", onClick: (ev, close) => close() }, { label: "Refresh sample", kind: "primary", onClick: (ev, close) => { close(); campaignPreview(cid, (size || 5)); } }, ]}); } /* ==================== 12-STEP WIZARD ==================== */ async function openWizard(editId) { try { if (!OPT) await loadOptions(); } catch (e) { toast(e.message, "err", 6000); return; } editingId = editId || null; Object.assign(C, DEF()); step = 1; if (editingId) { try { const c = await GET(`/campaigns/${editingId}`); Object.assign(C, DEF(), { name: c.name, recipient_list_id: c.recipient_list_id, subject_set_ids: c.subject_set_ids || [], template_ids: c.template_ids || [], message_type: c.message_type, attach_mode: c.attach_mode, attach_pdf: c.attach_pdf, attach_pdf_template_id: c.attach_pdf_template_id, attachment_id: c.attachment_id, variable_config: c.variable_config || {}, account_ids: c.account_ids || [], rotation_strategy: c.rotation_strategy, rotation_weights: c.rotation_weights || {}, subject_rotation: c.subject_rotation, template_sequence_mode: c.template_sequence_mode, phone_selection: c.phone_selection, max_per_account: c.max_per_account, send_interval_seconds: c.send_interval_seconds, rate_per_minute: c.rate_per_minute, max_retries: c.max_retries, retry_delay_seconds: c.retry_delay_seconds, unsubscribe_enabled: c.unsubscribe_enabled, preview_sample_size: c.preview_sample_size, }); } catch (e) { toast(e.message, "err"); return; } } const wrap = el("div", {}, el("div", { class: "wiz-steps", id: "wSteps" }), el("div", { class: "step-body", id: "wBody" }), el("div", { class: "wiz-foot" }, el("button", { class: "btn secondary", id: "wCancel", onclick: () => closeModal() }, "Cancel"), el("div", { class: "flex", style: "gap:8px" }, el("button", { class: "btn secondary", id: "wBack" }, "‹ Back"), el("button", { class: "btn", id: "wNext" }, "Next ›")))); openModal(editingId ? `Edit campaign #${editingId}` : "New campaign — 12-step wizard", wrap, { xw: true, onClose: () => stopWizardWatch() }); $("#wBack").onclick = () => { if (step > 1) { step--; renderStep(); } }; $("#wNext").onclick = next; renderStep(); } function closeModal() { const x = $(".modal-back.open .x-btn"); if (x) x.click(); } function stopWizardWatch() {} function renderStepsBar() { $("#wSteps").innerHTML = STEPS.map((s, i) => { const n = i + 1; const cls = n === step ? "active" : (n < step ? "done" : ""); return `
${n < step ? "✓" : n}${esc(s)}
`; }).join(""); $$("#wSteps .wstep.done").forEach(w => w.onclick = () => { step = +w.dataset.step; renderStep(); }); } function numRow(label, key, attrs = "") { return `
`; } function renderStep() { renderStepsBar(); const b = $("#wBody"); $("#wBack").style.visibility = step > 1 ? "visible" : "hidden"; $("#wNext").textContent = step === 12 ? (editingId ? "Save changes" : "Create & start later") : "Next ›"; const H = t => `

${t}

`; const hint = t => `

${t}

`; if (step === 1) { b.innerHTML = H("Step 1 — Campaign name") + hint("A clear, unique name for this campaign. It appears in logs and exports.") + ` `; } else if (step === 2) { b.innerHTML = H("Step 2 — Recipient list") + hint(`Pick the list to send to. Max ${OPT.limits.max_recipients} recipients per campaign; only valid, non-suppressed rows are used.`) + pickList("lists", C.recipient_list_id, l => ({ v: l.id, main: l.name, sub: `${l.row_count} rows · ${l.valid_count} valid` }), "recipient_list_id", true); } else if (step === 3) { b.innerHTML = H("Step 3 — Subject lines") + hint(`Select subject line variations (up to ${OPT.limits.max_subjects}) and how they rotate across recipients.`) + pickList("subjects", C.subject_set_ids, s => ({ v: s.id, main: s.subject, sub: "" }), "subject_set_ids") + `
${["round_robin", "random", "fixed_first"].map(m => ` `).join("")}
`; } else if (step === 4) { b.innerHTML = H("Step 4 — HTML templates") + hint("Select one or more templates and how they sequence across recipients.") + pickList("templates", C.template_ids, t => ({ v: t.id, main: t.name, sub: "" }), "template_ids") + `
${["repeat", "stop", "random"].map(m => ` `).join("")}
`; } else if (step === 5) { const am = C.attach_mode; b.innerHTML = H("Step 5 — Attachments") + hint("Choose what to attach. PDF mode renders a template to a unique Invoice PDF per recipient.") + `
${[["none", "No attachment"], ["pdf", "Generated PDF invoice"], ["file", "Stored file attachment"], ["image", "Image attachment"]].map(([m, t]) => ` `).join("")}
`; bindAttExtra(); } else if (step === 6) { const vc = C.variable_config; b.innerHTML = H("Step 6 — Variables & random amounts") + hint("Configure the invoice number format and random amount ranges used by %RandomInvoiceNumber%, %RandomTotal% and %RandomShippingCharge%.") + `

Tokens: {prefix} {R#} upper {r#} lower {d#} digits {seq4} {date} {hash}. Crypto-strong, collision-checked per campaign.