File size: 24,822 Bytes
038574d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
const state = {
  view: location.hash.slice(1) || "overview",
  apiKey: sessionStorage.getItem("maester_api_key") || "",
  workspaceId: "",
  campaigns: [], variants: [], jobs: [], assets: [], calendar: [], workspaces: [],
  accounts: [], integrations: [], experiments: [], rules: [], audit: [], overview: {}, selectedVariantId: null,
};
const $ = (query, root = document) => root.querySelector(query);
const $$ = (query, root = document) => [...root.querySelectorAll(query)];
const escapeHtml = (value = "") => String(value).replace(/[&<>'"]/g, char => ({"&":"&amp;","<":"&lt;",">":"&gt;","'":"&#39;",'"':"&quot;"}[char]));
const badge = value => `<span class="badge ${escapeHtml(value)}">${escapeHtml(String(value).replaceAll("_", " "))}</span>`;
const date = value => value ? new Date(value).toLocaleString() : "-";
const tags = values => `<div class="platforms">${(values || []).map(value => `<span class="platform">${escapeHtml(String(value).replaceAll("_", " "))}</span>`).join("")}</div>`;
const jsonPost = body => ({method: "POST", body: JSON.stringify(body)});

function toast(message, error = false) {
  const node = document.createElement("div");
  node.className = `toast${error ? " error" : ""}`;
  node.textContent = message;
  $("#toast-region").append(node);
  setTimeout(() => node.remove(), 4000);
}

async function api(path, options = {}) {
  const headers = new Headers(options.headers || {});
  if (state.apiKey) headers.set("X-API-Key", state.apiKey);
  if (options.body && !(options.body instanceof FormData)) headers.set("Content-Type", "application/json");
  const response = await fetch(path, {...options, headers, credentials: "same-origin"});
  if (response.status === 401) {
    $("#settings-dialog").showModal();
    throw new Error("API key required");
  }
  if (!response.ok) {
    const body = await response.json().catch(() => ({detail: response.statusText}));
    throw new Error(typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail));
  }
  return response.status === 204 ? null : response.json();
}

async function loadData() {
  const scope = state.workspaceId ? `?workspace_id=${encodeURIComponent(state.workspaceId)}` : "";
  const values = await Promise.all([
    api(`/automation/overview${scope}`), api("/automation/campaigns"), api("/automation/variants"), api("/automation/jobs"),
    api("/automation/assets"), api("/automation/calendar"), api("/automation/workspaces"), api("/automation/accounts"),
    api("/automation/integrations"), api("/automation/experiments"), api("/automation/rules"), api("/automation/audit?limit=100"),
  ]);
  [state.overview, state.campaigns, state.variants, state.jobs, state.assets, state.calendar, state.workspaces, state.accounts, state.integrations, state.experiments, state.rules, state.audit] = values;
  $(".status-dot").classList.add("online");
  $("#connection-label").textContent = "Gateway online";
  fillSelectors();
}

function fillSelectors() {
  $("#workspace-filter").innerHTML = `<option value="">All workspaces</option>${state.workspaces.map(item => `<option value="${item.id}">${escapeHtml(item.name)}</option>`).join("")}`;
  $("#workspace-filter").value = state.workspaceId;
  const campaignOptions = state.campaigns.map(item => `<option value="${item.id}">${escapeHtml(item.name)}</option>`).join("");
  const editorValue = $("#editor-campaign-filter").value;
  $("#editor-campaign-filter").innerHTML = `<option value="">All campaigns</option>${campaignOptions}`;
  $("#editor-campaign-filter").value = editorValue;
  $("#calendar-campaign").innerHTML = campaignOptions;
  const variantOptions = state.variants.map(item => `<option value="${item.id}">${escapeHtml(item.title)} · ${escapeHtml(item.platform)}</option>`).join("");
  $("#analytics-variant").innerHTML = variantOptions;
  $("#review-variants").innerHTML = variantOptions;
}

function metric(label, value, detail = "") {
  return `<div class="metric"><div class="metric-label">${label}</div><div class="metric-value">${value}</div><div class="metric-detail">${escapeHtml(detail)}</div></div>`;
}

function renderOverview() {
  const data = state.overview;
  $("#overview-metrics").innerHTML = metric("Campaigns", data.campaigns || 0, "Production briefs") + metric("Variants", data.variants || 0, "Platform cuts") + metric("Queued", data.queue?.states?.queued || 0, `${data.queue?.total || 0} jobs`) + metric("Published", data.approvals?.published || 0, "Live variants");
  const queue = data.queue?.states || {};
  $("#overview-queue").innerHTML = Object.keys(queue).length ? Object.entries(queue).map(([name, count]) => `<div class="progress-row"><span>${escapeHtml(name)}</span><div class="progress"><span style="width:${count / Math.max(data.queue.total, 1) * 100}%"></span></div><strong>${count}</strong></div>`).join("") : `<div class="empty-state">Queue is clear</div>`;
  const scores = data.analytics?.platform_average_scores || {};
  $("#overview-performance").innerHTML = Object.keys(scores).length ? Object.entries(scores).map(([name, score]) => `<div class="progress-row"><span>${escapeHtml(name)}</span><div class="progress"><span style="width:${Math.min(score * 4, 100)}%"></span></div><strong>${score}</strong></div>`).join("") : `<div class="empty-state">No performance data</div>`;
  $("#overview-approvals").innerHTML = `<div class="metric-grid">${Object.entries(data.approvals || {}).map(([name, count]) => metric(name.replaceAll("_", " "), count)).join("")}</div>`;
}

function renderCampaigns() {
  const counts = state.variants.reduce((result, item) => ({...result, [item.campaign_id]: (result[item.campaign_id] || 0) + 1}), {});
  $("#campaign-table").innerHTML = state.campaigns.map(item => `<tr><td><strong>${escapeHtml(item.name)}</strong><div class="muted">${date(item.updated_at)}</div></td><td>${escapeHtml(item.topic)}</td><td>${tags(item.platforms)}</td><td>${counts[item.id] || 0}</td><td>${badge(item.status)}</td><td><div class="table-actions"><button class="command" data-action="open-campaign" data-id="${item.id}">Edit</button><button class="command" data-action="generate-ai" data-id="${item.id}">AI generate</button><button class="command" data-action="schedule-campaign" data-id="${item.id}">Schedule</button></div></td></tr>`).join("") || `<tr><td colspan="6"><div class="empty-state">No campaigns</div></td></tr>`;
}

function renderEditor() {
  const campaignId = $("#editor-campaign-filter").value;
  const variants = state.variants.filter(item => !campaignId || item.campaign_id === campaignId);
  $("#variant-list").innerHTML = variants.map(item => `<button class="selection-item ${item.id === state.selectedVariantId ? "active" : ""}" data-action="select-variant" data-id="${item.id}"><span class="selection-title">${escapeHtml(item.title)}</span><span class="selection-meta"><span>${escapeHtml(item.platform)}</span>${badge(item.approval_state)}</span></button>`).join("") || `<div class="empty-state">No variants</div>`;
  const item = state.variants.find(variant => variant.id === state.selectedVariantId);
  if (!item) return;
  $("#variant-editor").innerHTML = `<form id="variant-form"><div class="section-head"><h2>${escapeHtml(item.title)}</h2>${badge(item.approval_state)}</div><div class="form-grid"><label class="span-2">Title<input name="title" value="${escapeHtml(item.title)}"></label><label class="span-2">Hook<textarea name="hook" rows="3">${escapeHtml(item.hook)}</textarea></label><label class="span-2">Script<textarea name="script" rows="10">${escapeHtml(item.script)}</textarea></label><label class="span-2">Caption<textarea name="caption" rows="4">${escapeHtml(item.caption)}</textarea></label><label>Hashtags<input name="hashtags" value="${escapeHtml(item.hashtags.join(", "))}"></label><label>Call to action<input name="cta" value="${escapeHtml(item.cta)}"></label><label>Template<input name="template" value="${escapeHtml(item.template)}"></label><label>Style<input name="creative_style" value="${escapeHtml(item.creative_style)}"></label><label>Duration<input name="duration_seconds" type="number" min="3" max="600" value="${item.duration_seconds}"></label></div><div class="editor-actions"><button class="command primary" type="submit">Save changes</button><button class="command" type="button" data-action="approve-variant" data-id="${item.id}">✓ Approve</button><button class="command" type="button" data-action="render-variant" data-id="${item.id}">▶ Render</button><button class="command" type="button" data-action="publish-variant" data-id="${item.id}">Publish draft</button><button class="command" type="button" data-action="rewrite-variant" data-id="${item.id}">Rewrite</button></div></form>`;
  $("#variant-form").addEventListener("submit", saveVariant);
  renderInspector(item);
}

async function renderInspector(item) {
  try {
    const quality = await api(`/automation/quality/variants/${item.id}`);
    $("#variant-inspector").innerHTML = `<div class="section-head"><h2>Inspector</h2>${badge(item.job_state)}</div><div class="quality-grid"><div class="quality"><strong>${quality.hook.score}</strong><span>Hook</span></div><div class="quality"><strong>${quality.script.score}</strong><span>Script</span></div><div class="quality"><strong>${quality.caption.score}</strong><span>Caption</span></div><div class="quality"><strong>${Math.round(quality.retention.predicted_completion_rate * 100)}%</strong><span>Completion</span></div></div><h3>Render payload</h3><div class="json-box">${escapeHtml(JSON.stringify(item.render_payload, null, 2))}</div>`;
  } catch (error) { $("#variant-inspector").innerHTML = `<div class="empty-state">${escapeHtml(error.message)}</div>`; }
}

function renderCalendar() {
  const variants = Object.fromEntries(state.variants.map(item => [item.id, item]));
  $("#calendar-table").innerHTML = state.calendar.sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at)).map(item => `<tr><td>${date(item.scheduled_at)}</td><td>${tags([item.platform])}</td><td>${escapeHtml(variants[item.variant_id]?.title || item.variant_id)}</td><td>${badge(item.status)}</td></tr>`).join("") || `<tr><td colspan="4"><div class="empty-state">No scheduled content</div></td></tr>`;
}

function renderJobs() {
  const filter = $("#job-filter").value;
  $("#job-table").innerHTML = state.jobs.filter(item => !filter || item.state === filter).map(item => `<tr><td><strong>${escapeHtml(item.id)}</strong><div class="muted">${escapeHtml(item.idempotency_key || "")}</div></td><td>${escapeHtml(item.kind)}</td><td>${badge(item.state)}</td><td>${item.attempts}/${item.max_attempts}</td><td>${date(item.updated_at)}</td><td><div class="table-actions">${["failed", "cancelled"].includes(item.state) ? `<button class="command" data-action="retry-job" data-id="${item.id}">↻</button>` : ""}${!["published", "ready", "failed", "cancelled"].includes(item.state) ? `<button class="command danger" data-action="cancel-job" data-id="${item.id}">×</button>` : ""}</div></td></tr>`).join("") || `<tr><td colspan="6"><div class="empty-state">No matching jobs</div></td></tr>`;
}

function renderAnalytics() {
  const data = state.overview.analytics || {}, winners = data.winners || [];
  $("#analytics-metrics").innerHTML = metric("Winners", winners.length, "Viral score ≥ 20") + metric("Platforms", Object.keys(data.platform_average_scores || {}).length, "Reporting sources") + metric("Experiments", state.experiments.length, "Controlled tests") + metric("Top score", winners[0]?.score || 0, winners[0]?.platform || "No data");
  $("#winner-board").innerHTML = winners.length ? `<div class="stack-list">${winners.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.title)}</strong><small>${escapeHtml(item.platform)}</small></div>${badge(String(item.score))}</div>`).join("")}</div>` : `<div class="empty-state">No winners yet</div>`;
  $("#experiment-list").innerHTML = state.experiments.length ? `<div class="stack-list">${state.experiments.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.name)}</strong><small>${escapeHtml(item.variable)} · ${item.variant_ids.length} variants</small></div>${badge(item.status)}<button class="command" data-action="evaluate-experiment" data-id="${item.id}">Evaluate</button></div>`).join("")}</div>` : `<div class="empty-state">No experiments</div>`;
}

function renderAssets() {
  $("#asset-table").innerHTML = state.assets.map(item => `<tr><td><strong>${escapeHtml(item.filename)}</strong><div class="muted">${escapeHtml(item.checksum.slice(0, 12))}</div></td><td>${badge(item.kind)}</td><td>${Math.ceil(item.size_bytes / 1024)} KB</td><td>${escapeHtml(item.license)}<div class="muted">${escapeHtml(item.attribution)}</div></td><td>${tags(item.tags)}</td><td><button class="command danger" data-action="delete-asset" data-id="${item.id}">Delete</button></td></tr>`).join("") || `<tr><td colspan="6"><div class="empty-state">No assets</div></td></tr>`;
}

function renderReviews() {
  const pending = state.variants.filter(item => ["draft", "needs_review"].includes(item.approval_state));
  $("#pending-reviews").innerHTML = pending.length ? `<div class="stack-list">${pending.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.title)}</strong><small>${escapeHtml(item.platform)}</small></div>${badge(item.approval_state)}</div>`).join("")}</div>` : `<div class="empty-state">Nothing awaiting review</div>`;
}

function renderIntegrations() {
  $("#account-list").innerHTML = state.accounts.length ? `<div class="stack-list">${state.accounts.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.display_name || item.handle)}</strong><small>${escapeHtml(item.platform)} · ${escapeHtml(item.auth_status)}</small></div><button class="command" data-action="sync-account" data-id="${item.id}">Sync</button></div>`).join("")}</div>` : `<div class="empty-state">No connected accounts</div>`;
  $("#integration-list").innerHTML = state.integrations.length ? `<div class="stack-list">${state.integrations.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.name)}</strong><small>${escapeHtml(item.kind)}</small></div>${badge(item.active ? "ready" : "disabled")}</div>`).join("")}</div>` : `<div class="empty-state">No delivery integrations</div>`;
  $("#rule-list").innerHTML = state.rules.length ? `<div class="stack-list">${state.rules.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.name)}</strong><small>${escapeHtml(item.event)} · ${item.actions.length} actions</small></div>${badge(item.active ? "ready" : "disabled")}</div>`).join("")}</div>` : `<div class="empty-state">No automation rules</div>`;
}

async function renderOperations() {
  const [providers, services] = await Promise.all([api("/automation/providers"), api("/services")]);
  $("#ops-metrics").innerHTML = metric("Workspaces", state.workspaces.length, "Tenant boundaries") + metric("Accounts", state.accounts.length, "Publishing targets") + metric("Assets", state.assets.length, "Managed media") + metric("Audit events", state.audit.length, "Recent operations");
  $("#provider-status").innerHTML = `<div class="stack-list">${Object.entries(providers.providers).map(([name, item]) => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(name)}</strong><small>${escapeHtml(item.model)}</small></div>${badge(item.configured ? "ready" : "not_configured")}</div>`).join("")}</div>`;
  $("#service-status").innerHTML = `<div class="stack-list">${services.services.map(item => `<div class="stack-row"><div class="stack-row-main"><strong>${escapeHtml(item.title)}</strong><small>${escapeHtml(item.path)}</small></div>${badge(item.mounted ? "ready" : item.enabled ? "failed" : "disabled")}</div>`).join("")}</div>`;
  $("#audit-table").innerHTML = state.audit.map(item => `<tr><td>${date(item.created_at)}</td><td>${escapeHtml(item.actor)}</td><td>${escapeHtml(item.action)}</td><td>${escapeHtml(item.resource_type)} · ${escapeHtml(item.resource_id)}</td></tr>`).join("") || `<tr><td colspan="4"><div class="empty-state">No audit events</div></td></tr>`;
}

const renderers = {overview: renderOverview, campaigns: renderCampaigns, editor: renderEditor, calendar: renderCalendar, jobs: renderJobs, analytics: renderAnalytics, assets: renderAssets, reviews: renderReviews, integrations: renderIntegrations, operations: renderOperations};
async function refresh() { try { await loadData(); await renderers[state.view]?.(); } catch (error) { $(".status-dot").classList.remove("online"); $("#connection-label").textContent = "Gateway unavailable"; toast(error.message, true); } }
function switchView(view) { state.view = view; location.hash = view; $$(".view").forEach(item => item.classList.toggle("active", item.id === `view-${view}`)); $$(".nav-item").forEach(item => item.classList.toggle("active", item.dataset.view === view)); $("#view-title").textContent = view[0].toUpperCase() + view.slice(1); $("#sidebar").classList.remove("open"); renderers[view]?.(); }

async function saveVariant(event) {
  event.preventDefault();
  const data = Object.fromEntries(new FormData(event.currentTarget));
  data.hashtags = data.hashtags.split(",").map(item => item.trim()).filter(Boolean);
  data.duration_seconds = Number(data.duration_seconds);
  try { await api(`/automation/variants/${state.selectedVariantId}`, {method: "PATCH", body: JSON.stringify(data)}); toast("Variant saved"); await refresh(); } catch (error) { toast(error.message, true); }
}

async function action(name, id) {
  try {
    if (name === "process-queue") { const result = await api("/automation/queue/process", {method: "POST"}); toast(`${result.count} job processed`); }
    if (name === "select-variant") { state.selectedVariantId = id; renderEditor(); return; }
    if (name === "open-campaign") { $("#editor-campaign-filter").value = id; state.selectedVariantId = state.variants.find(item => item.campaign_id === id)?.id; switchView("editor"); return; }
    if (name === "generate-ai") await api(`/automation/campaigns/${id}/generate-ai`, jsonPost({}));
    if (name === "schedule-campaign") await api(`/automation/schedule/apply?campaign_id=${id}`, {method: "POST"});
    if (name === "apply-schedule") { const start = $("#calendar-start").value; await api(`/automation/schedule/apply?campaign_id=${$("#calendar-campaign").value}${start ? `&start_at=${encodeURIComponent(new Date(start).toISOString())}` : ""}`, {method: "POST"}); }
    if (name === "approve-variant") await api(`/automation/variants/${id}/approval`, jsonPost({state: "approved", reviewer: "Studio operator", notes: "Approved in Content Studio"}));
    if (name === "render-variant") await api(`/automation/variants/${id}/render-job`, {method: "POST"});
    if (name === "publish-variant") await api("/automation/publish", jsonPost({variant_id: id, draft: true}));
    if (name === "rewrite-variant") await api(`/automation/variants/${id}/rewrite`, {method: "POST"});
    if (name === "retry-job") await api(`/automation/jobs/${id}/retry`, {method: "POST"});
    if (name === "cancel-job") await api(`/automation/jobs/${id}/cancel`, {method: "POST"});
    if (name === "delete-asset") await api(`/automation/assets/${id}`, {method: "DELETE"});
    if (name === "sync-account") await api(`/automation/accounts/${id}/analytics-sync`, {method: "POST"});
    if (name === "evaluate-experiment") await api(`/automation/experiments/${id}/evaluate`, {method: "POST"});
    if (name === "new-experiment") { const campaign = state.campaigns[0]; if (!campaign) throw new Error("Create a campaign first"); await api("/automation/experiments", jsonPost({campaign_id: campaign.id, name: `${campaign.name} hook test`, variable: "hook", variant_ids: state.variants.filter(item => item.campaign_id === campaign.id).map(item => item.id)})); }
    if (name === "new-rule") await api("/automation/rules", jsonPost({name: "Render approved variants", event: "variant_approved", actions: [{type: "render"}], active: true}));
    toast("Operation completed"); await refresh();
  } catch (error) { toast(error.message, true); }
}

document.addEventListener("click", event => { const nav = event.target.closest("[data-view]"); if (nav) switchView(nav.dataset.view); const dialog = event.target.closest("[data-dialog]"); if (dialog) $(`#${dialog.dataset.dialog}`).showModal(); const button = event.target.closest("[data-action]"); if (button) action(button.dataset.action, button.dataset.id); });
$("#menu-button").addEventListener("click", () => $("#sidebar").classList.toggle("open"));
$("#refresh-button").addEventListener("click", refresh);
$("#settings-button").addEventListener("click", () => $("#settings-dialog").showModal());
$("#settings-form").addEventListener("submit", event => { event.preventDefault(); state.apiKey = $("#api-key").value.trim(); sessionStorage.setItem("maester_api_key", state.apiKey); $("#settings-dialog").close(); refresh(); });
$("#workspace-filter").addEventListener("change", event => { state.workspaceId = event.target.value; refresh(); });
$("#editor-campaign-filter").addEventListener("change", renderEditor);
$("#job-filter").addEventListener("change", renderJobs);

$("#campaign-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = Object.fromEntries(form); data.platforms = form.getAll("platforms"); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.brand_kit_id ||= null; try { await api("/automation/campaigns/generate", jsonPost(data)); event.currentTarget.reset(); $("#campaign-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#repurpose-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.platforms = ["tiktok", "instagram_reels", "youtube_shorts"]; try { await api("/automation/repurpose", jsonPost(data)); event.currentTarget.reset(); $("#repurpose-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#analytics-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); ["views", "likes", "comments", "shares"].forEach(key => data[key] = Number(data[key])); data.completion_rate = Number(data.completion_rate); try { await api("/automation/analytics/events", jsonPost(data)); await refresh(); } catch (error) { toast(error.message, true); } });
$("#asset-form").addEventListener("submit", async event => { event.preventDefault(); const data = new FormData(event.currentTarget); if (state.workspaceId) data.set("workspace_id", state.workspaceId); try { await api("/automation/assets", {method: "POST", body: data}); event.currentTarget.reset(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#review-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = {variant_ids: form.getAll("variant_ids"), expires_in_days: Number(form.get("expires_in_days")), created_by: form.get("created_by"), workspace_id: state.workspaceId || null}; try { const result = await api("/automation/reviews", jsonPost(data)); const url = new URL(result.review_url, location.origin).href; $("#review-result").innerHTML = `<div class="link-output"><strong>Review link</strong><br><a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(url)}</a></div>`; await refresh(); } catch (error) { toast(error.message, true); } });
$("#account-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; data.metadata = {token_env: data.token_env, publish_endpoint: data.publish_endpoint, analytics_endpoint: data.analytics_endpoint}; delete data.token_env; delete data.publish_endpoint; delete data.analytics_endpoint; try { await api("/automation/accounts", jsonPost(data)); $("#account-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#integration-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; try { await api("/automation/integrations", jsonPost(data)); $("#integration-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#api-key").value = state.apiKey;
switchView(state.view in renderers ? state.view : "overview");
refresh();