';
try {
const res = await fetch(AFFILIATE_LINKS_URL);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
dbg("\u2190 Affiliate links", data);
renderAffiliateLinks(data);
} catch (err) {
dbg("\u2190 Affiliate links error", err.message);
body.innerHTML =
'
Could not load affiliate links.
';
}
}
function renderAffiliateLinks(data) {
const body = document.getElementById("affiliate-links-body");
if (!body) return;
let areas = [];
if (data && Array.isArray(data.areas)) {
areas = data.areas;
} else if (data && Array.isArray(data.data)) {
const first = data.data[0];
if (first && Array.isArray(first.areas)) areas = first.areas;
} else if (Array.isArray(data)) {
const first = data[0];
if (first && Array.isArray(first.areas)) areas = first.areas;
}
body.innerHTML = `
${areas.length} areas
${areas
.map(
(a) => `
${escHtml(a.area)}
`,
)
.join("")}
`;
}
function affiliateAreaChanged() {
const btn = document.getElementById("save-affiliate-btn");
if (btn) btn.style.display = "inline-flex";
}
async function saveAffiliateLinks() {
const btn = document.getElementById("save-affiliate-btn");
if (!btn) return;
btnLoad(btn, "Saving…");
try {
const inputs = document.querySelectorAll(".affiliate-input");
const map = {};
for (const inp of inputs) {
const area = inp.dataset.area;
const field = inp.dataset.field;
if (!map[area]) map[area] = { area, text: "", link: "" };
map[area][field] = inp.value;
}
const areas = Object.values(map);
const res = await fetch(AFFILIATE_LINKS_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ areas }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
toast("Affiliate links saved.", "success");
btn.style.display = "none";
loadAffiliateLinks();
} catch (err) {
toast(err.message, "error");
} finally {
btnReset(btn);
}
}
/* ═══════════════════════════════════════════════════════════════
RESTART
═══════════════════════════════════════════════════════════════ */
async function doRestart() {
const ok = await confirmDialog(
"Restart Agent",
"Your agent will be unavailable for a few minutes while it restarts. This will interrupt any active sessions. Continue?",
);
if (!ok) return;
const btn = document.getElementById("restart-btn");
btnLoad(btn, "Restarting\u2026");
try {
dbg("→ Restart Agent", RESTART_URL);
const res = await apiFetch(RESTART_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
uuid: activeUUID,
email: currentEmail,
action: "restart",
}),
});
if (!res.ok)
throw new Error(`Restart request failed (HTTP ${res.status}).`);
toast(
"Restart initiated \u2014 your agent will be back shortly.",
"success",
);
} catch (err) {
toast(err.message, "error");
} finally {
// Rebuild button with its SVG icon
btn.disabled = false;
btn.innerHTML = ` Restart Agent`;
}
}
/* ═══════════════════════════════════════════════════════════════
BACKUPS — load, render, create, restore
═══════════════════════════════════════════════════════════════ */
async function loadBackups(uuid) {
const body = document.getElementById("backups-body");
body.innerHTML = '
';
// Check BACKUP flag from agent info response
const backupRaw = activeAgentInfo ? stripQuotes(activeAgentInfo.backup) : "—";
const hasBackup =
backupRaw !== "—" && backupRaw !== "0" && backupRaw !== "false";
const btn = document.getElementById("make-backup-btn");
if (btn) {
if (hasBackup) {
btn.style.display = "";
btn.innerHTML = "+";
btn.onclick = makeBackup;
btn.title = "Make Backup";
} else {
btn.style.display = "";
btn.innerHTML = "+";
btn.onclick = openBillingPortal;
btn.title = "Upgrade to enable backups";
btn.style.fontSize = "";
btn.style.lineHeight = "";
btn.style.padding = "";
}
}
try {
const _backupsUrl = `${BACKUP_URL}?uuid=${encodeURIComponent(uuid)}`;
dbg("→ List Backups", _backupsUrl);
const res = await apiFetch(_backupsUrl);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
let data = [];
try {
data = await res.json();
} catch (_) {}
dbg("← List Backups", data);
// Response: { uuid, backup_path, archives: [{name, start, end, id}] }
const archives =
data && Array.isArray(data.archives)
? data.archives
: Array.isArray(data)
? data
: [];
renderBackups(archives);
} catch (err) {
body.innerHTML = `
${escHtml(err.message)}
`;
}
}
function renderBackups(backups) {
const body = document.getElementById("backups-body");
if (!backups.length) {
body.innerHTML = `
No backups yet. Click Make Backup to create one.
`;
return;
}
const rows = [...backups]
.sort((a, b) => {
const da = new Date(
a.start || a.created_at || a.createdAt || a.date || a.timestamp || 0,
);
const db = new Date(
b.start || b.created_at || b.createdAt || b.date || b.timestamp || 0,
);
return db - da;
})
.map((b) => {
const start = formatBackupDate(b.start);
const id = escAttr(String(b.id || b.name || ""));
const nm = escAttr(String(b.name || b.id || start));
return `
${escHtml(start)}
`;
})
.join("");
body.innerHTML = `
${rows}
`;
}
async function makeBackup() {
const btn = document.getElementById("make-backup-btn");
btnLoad(btn, "…");
try {
dbg("→ Make Backup", BACKUP_URL);
const res = await apiFetch(BACKUP_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ uuid: activeUUID }),
});
if (!res.ok)
throw new Error(`Failed to create backup (HTTP ${res.status}).`);
toast("Backup created successfully!", "success");
} catch (err) {
toast(err.message, "error");
} finally {
btnReset(btn);
await loadBackups(activeUUID);
}
}
function restoreBackupFromBtn(btn) {
restoreBackup({ id: btn.dataset.backupId, name: btn.dataset.backupName });
}
async function restoreBackup(backup) {
const ok = await confirmDialog(
"Restore Backup",
`Restore "${backup.name}"? Your server data will be replaced with this backup and the agent will restart briefly.`,
);
if (!ok) return;
try {
dbg("→ Restore Backup", BACKUP_URL);
const res = await apiFetch(BACKUP_URL, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
uuid: activeUUID,
email: currentEmail,
action: "restore",
archive_name: backup.name,
}),
});
if (!res.ok) throw new Error(`Restore failed (HTTP ${res.status}).`);
toast(
"Restore initiated \u2014 your agent will be back shortly.",
"success",
);
} catch (err) {
toast(err.message, "error");
}
}
/* ═══════════════════════════════════════════════════════════════
CONTRACT
═══════════════════════════════════════════════════════════════ */
async function loadContract(uuid) {
const body = document.getElementById("contract-body");
body.innerHTML = '
';
document.getElementById("contract-status-dot").style.visibility = "hidden";
try {
const url = `${CONTRACT_URL}?uuid=${encodeURIComponent(uuid)}`;
dbg("\u2192 Contract", url);
const res = await apiFetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
dbg("\u2190 Contract", data);
renderContract(Array.isArray(data) ? data : [data]);
} catch (err) {
body.innerHTML = `
${escHtml(err.message)}
`;
}
}
function renderContract(data) {
const body = document.getElementById("contract-body");
const headerDot = document.getElementById("contract-status-dot");
if (!data || !data.length) {
body.innerHTML =
'
No contract data.
';
return;
}
const item = data[0];
const product = stripQuotes(item.product || "");
const period = stripQuotes(item.period || "");
const type = stripQuotes(item.type || "");
// Look up expires from the agent data (new field in agents list API)
const agent = agents.find((a) => (a.UUID || a.uuid) === activeUUID);
const rawExpires =
agent && agent.expires
? String(agent.expires).replace(/^"|"$/g, "").trim()
: "";
const hasExpiry = rawExpires && rawExpires !== "null" && rawExpires !== "";
const expires = hasExpiry ? rawExpires : "";
const label =
product && period
? `${product} \u2013 ${period}`
: product || period || type || "—";
headerDot.className = hasExpiry
? "contract-dot contract-dot--red"
: "contract-dot contract-dot--green";
headerDot.style.visibility = "visible";
let html = `
${escHtml(label)}`;
if (hasExpiry) {
html += `
expires ${escHtml(expires)}`;
}
html += `
`;
if (hasExpiry) {
html += `
Resubscribe →`;
}
html += `
`;
body.innerHTML = html;
}
/* ═══════════════════════════════════════════════════════════════
WIZARD (Integrations setup)
═══════════════════════════════════════════════════════════════ */
function loadWizard(info) {
// Check both key casings; strip n8n's extra surrounding quotes before comparing
const rawVal = info.wizzard ?? info.WIZZARD;
const strVal = String(rawVal ?? "")
.replace(/^"|"$/g, "")
.trim()
.toLowerCase();
const disabled = rawVal === false || strVal === "false";
const tb = document.getElementById("wizard-toggle-btn");
if (disabled) {
hide("wizard-card");
if (tb) tb.textContent = "Integrations";
return;
}
// Reset selection state each time (JSON data cached across calls)
wizardPhase = "skills";
wizardSelectedSkills = new Set();
wizardSelectedIntegrations = new Set();
wizardFieldValues = {};
wizardIntegrationStep = 0;
wizardSelectedIntegrationList = [];
show("wizard-card");
if (tb) tb.textContent = "Backup";
renderWizardStep();
}
async function renderWizardStep() {
const body = document.getElementById("wizard-body");
if (wizardPhase === "skills") {
body.innerHTML =
'
';
try {
if (!wizardSkillsData) {
const res = await fetch("skills/index.json");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
wizardSkillsData = await res.json();
}
await Promise.all(
wizardSkillsData.map(async (file) => {
if (!wizardSkillsCache[file]) {
const res = await fetch(`skills/${file}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
wizardSkillsCache[file] = await res.json();
}
}),
);
} catch (err) {
body.innerHTML = `
Your init prompt is ready! Copy it and paste it directly into your Hermes instance — it will automatically install and configure all selected skills and integrations for you.
`;
}
function buildWizardPrompt() {
const parts = [];
const skillLines = [];
for (const i of wizardSelectedSkills) {
const file = wizardSkillsData && wizardSkillsData[i];
const skill = file && wizardSkillsCache[file];
if (skill && skill.prompt) skillLines.push(skill.prompt);
}
if (skillLines.length) parts.push("## Skills\n\n" + skillLines.join("\n\n"));
const intLines = [];
for (
let stepIdx = 0;
stepIdx < wizardSelectedIntegrationList.length;
stepIdx++
) {
const file = wizardSelectedIntegrationList[stepIdx];
const integration = wizardIntegrationsCache[file];
if (!integration) continue;
const values = wizardFieldValues[stepIdx] || {};
let prompt = integration.prompt || "";
for (const [k, v] of Object.entries(values)) {
prompt = prompt.split(`{${k}}`).join(v || `{${k}}`);
}
intLines.push(`### ${integration.name}\n${prompt}`);
}
if (intLines.length)
parts.push("## Integrations\n\n" + intLines.join("\n\n"));
return parts.join("\n\n---\n\n") || "(No skills or integrations selected.)";
}
function updateWizardNav() {
const backBtn = document.getElementById("wizard-back-btn");
const nextBtn = document.getElementById("wizard-next-btn");
const cardTitle = document.getElementById("wizard-card-title");
let label = "";
if (wizardPhase === "skills") label = "Select Skills";
else if (wizardPhase === "integrations") label = "Select Integrations";
else if (wizardPhase === "fields") {
const file = wizardSelectedIntegrationList[wizardIntegrationStep];
const int = file ? wizardIntegrationsCache[file] : null;
label = int ? int.name : "";
} else if (wizardPhase === "review") label = "Review Init Prompt";
if (cardTitle) cardTitle.textContent = label;
if (backBtn) {
backBtn.style.display = wizardPhase === "skills" ? "none" : "";
backBtn.onclick = wizardBack;
}
if (nextBtn) {
nextBtn.textContent =
wizardPhase === "review" ? "Copy & Finish" : "Next \u2192";
nextBtn.onclick = wizardNext;
}
}
function wizardToggleSkill(index, checked) {
if (checked) wizardSelectedSkills.add(index);
else wizardSelectedSkills.delete(index);
}
function wizardToggleIntegration(index, checked) {
if (checked) wizardSelectedIntegrations.add(index);
else wizardSelectedIntegrations.delete(index);
}
function wizardSetField(stepKey, fieldKey, value) {
if (!wizardFieldValues[stepKey]) wizardFieldValues[stepKey] = {};
wizardFieldValues[stepKey][fieldKey] = value;
}
function wizardFilter(input, listId) {
const q = input.value.toLowerCase().trim();
const list = document.getElementById(listId);
if (!list) return;
list.querySelectorAll(".wizard-list-item").forEach((item) => {
const name = (item.dataset.name || "").toLowerCase();
const desc = (item.dataset.desc || "").toLowerCase();
item.style.display =
!q || name.includes(q) || desc.includes(q) ? "" : "none";
});
}
function wizardNext() {
if (wizardPhase === "skills") {
wizardPhase = "integrations";
renderWizardStep();
} else if (wizardPhase === "integrations") {
wizardSelectedIntegrationList = (wizardIntegrationsData || []).filter(
(_, i) => wizardSelectedIntegrations.has(i),
);
wizardIntegrationStep = 0;
wizardPhase = wizardSelectedIntegrationList.length ? "fields" : "review";
renderWizardStep();
} else if (wizardPhase === "fields") {
wizardIntegrationStep++;
if (wizardIntegrationStep >= wizardSelectedIntegrationList.length) {
wizardPhase = "review";
}
renderWizardStep();
} else if (wizardPhase === "review") {
wizardCopyAndFinish();
}
}
function wizardBack() {
if (wizardPhase === "integrations") {
wizardPhase = "skills";
renderWizardStep();
} else if (wizardPhase === "fields") {
if (wizardIntegrationStep > 0) {
wizardIntegrationStep--;
} else {
wizardPhase = "integrations";
}
renderWizardStep();
} else if (wizardPhase === "review") {
if (wizardSelectedIntegrationList.length > 0) {
wizardPhase = "fields";
wizardIntegrationStep = wizardSelectedIntegrationList.length - 1;
} else {
wizardPhase = "integrations";
}
renderWizardStep();
}
}
async function wizardCopyAndFinish() {
const textarea = document.getElementById("wizard-prompt-textarea");
if (!textarea) return;
let copied = false;
try {
await navigator.clipboard.writeText(textarea.value);
copied = true;
} catch (_) {
toast("Could not copy to clipboard — please copy manually.", "error");
return;
}
// Mark wizard complete via the agent-info endpoint (non-fatal)
try {
await apiFetch(AGENT_INFO_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
uuid: activeUUID,
key: "WIZZARD",
value: "false",
}),
});
} catch (_) {
/* non-fatal */
}
// Show success screen inside the wizard instead of closing immediately
const cardTitle = document.getElementById("wizard-card-title");
if (cardTitle) cardTitle.textContent = "Prompt Copied";
document.getElementById("wizard-body").innerHTML = `
Your init prompt is in the clipboard.
Open your agent’s console and paste the prompt
(Ctrl + V or
Cmd + V)
to apply the configuration.
`;
const backBtn = document.getElementById("wizard-back-btn");
const nextBtn = document.getElementById("wizard-next-btn");
if (backBtn) backBtn.style.display = "none";
if (nextBtn) {
nextBtn.textContent = "Done";
nextBtn.onclick = () => {
hide("wizard-card");
const tb = document.getElementById("wizard-toggle-btn");
if (tb) tb.textContent = "Integrations";
};
}
}
function wizardDone() {
hide("wizard-card");
const tb = document.getElementById("wizard-toggle-btn");
if (tb) tb.textContent = "Integrations";
}
/* ═══════════════════════════════════════════════════════════════
CONFIRM DIALOG
═══════════════════════════════════════════════════════════════ */
let _confirmResolve = null;
function confirmDialog(title, message, okLabel = "Confirm") {
document.getElementById("confirm-title").textContent = title;
document.getElementById("confirm-message").innerHTML = message;
document.getElementById("confirm-ok-btn").textContent = okLabel;
document.getElementById("confirm-backdrop").classList.add("open");
return new Promise((resolve) => {
_confirmResolve = resolve;
});
}
function confirmClose(result) {
document.getElementById("confirm-backdrop").classList.remove("open");
if (_confirmResolve) {
_confirmResolve(result);
_confirmResolve = null;
}
}
/* ═══════════════════════════════════════════════════════════════
TOAST
═══════════════════════════════════════════════════════════════ */
function toast(msg, type = "info", duration = 4000) {
const el = document.createElement("div");
el.className = `toast toast-${type}`;
el.textContent = msg;
document.getElementById("toast-container").appendChild(el);
setTimeout(() => {
el.style.opacity = "0";
el.style.transform = "translateX(50px)";
setTimeout(() => el.remove(), 320);
}, duration);
}
/* ═══════════════════════════════════════════════════════════════
UTILITIES
═══════════════════════════════════════════════════════════════ */
/* ─── Cookie helpers ───────────────────────────────────────── */
function setCookie(name, value, days) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; domain=.derez.ai; SameSite=Lax`;
}
function getCookie(name) {
return document.cookie.split("; ").reduce((acc, c) => {
const [k, ...rest] = c.split("=");
return k === name ? decodeURIComponent(rest.join("=")) : acc;
}, null);
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.derez.ai; SameSite=Lax`;
}
/* ─── Authenticated fetch ───────────────────────────────────── */
// Wraps fetch() for all webhook calls — injects session header when logged in.
async function apiFetch(url, options = {}) {
const headers = {
...(options.headers || {}),
...(currentSession ? { "X-Session-Id": currentSession } : {}),
};
const res = await fetch(url, { ...options, headers });
if (res.status === 401) {
deleteCookie("al_session");
deleteCookie("al_email");
localStorage.removeItem("al_email");
location.reload();
}
return res;
}
function escHtml(s) {
if (s == null) return "";
return String(s)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function escAttr(s) {
return escHtml(s).replace(/'/g, "'");
}
function isValidEmail(s) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}
// Format backup start "2026-06-01T17:55:07.000000" → "2026-06-01 17:55"
function formatBackupDate(s) {
if (!s) return "—";
// Slice to minute precision and swap T for a space
const safe = String(s).slice(0, 16).replace("T", " ");
return safe.length >= 16 ? safe : "—";
}
function formatDate(d) {
if (!d) return "\u2014";
try {
return new Date(d).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch (_) {
return String(d);
}
}
function show(id) {
document.getElementById(id).style.display = "";
}
function hide(id) {
document.getElementById(id).style.display = "none";
}
/* Button loading helpers */
function btnLoad(btn, label) {
btn.disabled = true;
btn._origHTML = btn.innerHTML;
btn.innerHTML = ` ${label}`;
}
function btnReset(btn) {
btn.disabled = false;
btn.innerHTML = btn._origHTML || "";
}
/* Auth error helpers */
function showAuthError(id, msg) {
const el = document.getElementById(id);
el.textContent = msg;
el.style.display = "flex";
}
function hideAuthError(id) {
document.getElementById(id).style.display = "none";
if (id === "signin-error") {
const wrap = document.getElementById("reset-pw-wrap");
if (wrap) wrap.style.display = "none";
}
}
function showResetOption() {
const wrap = document.getElementById("reset-pw-wrap");
if (!wrap) return;
// Return to idle state in case it was previously used
document.getElementById("reset-pw-idle").style.display = "flex";
document.getElementById("reset-pw-sent").style.display = "none";
wrap.style.display = "block";
}
async function sendPasswordReset() {
const email = document.getElementById("signin-email").value.trim();
if (!email) {
toast("Enter your email address above first.", "warning");
return;
}
const btn = document.getElementById("reset-pw-btn");
btnLoad(btn, "Sending…");
try {
await fetch(PW_RESET_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
} catch (_) {
// Always show sent — never reveal whether the address exists
} finally {
// Hide the red error panel — it's no longer relevant once reset is requested
document.getElementById("signin-error").style.display = "none";
document.getElementById("reset-pw-idle").style.display = "none";
document.getElementById("reset-pw-sent").style.display = "flex";
}
}
/* ═══════════════════════════════════════════════════════════════
TALK TO US
═══════════════════════════════════════════════════════════════ */
function toggleTalk() {
talkOpen = !talkOpen;
document.getElementById("talk-panel").classList.toggle("open", talkOpen);
document
.getElementById("talk-toggle-btn")
.classList.toggle("active", talkOpen);
const chev = document.getElementById("talk-chevron");
chev.style.transform = talkOpen ? "rotate(180deg)" : "";
if (talkOpen) {
if (!chatId) {
chatId = String(Math.floor(100000 + Math.random() * 900000));
appendChatMsg("bot", "Hi! How can we help you today?");
}
document.getElementById("refer-own-email").textContent = currentEmail || "";
setTimeout(() => document.getElementById("chat-input").focus(), 280);
}
}
function appendChatMsg(from, text) {
const msgs = document.getElementById("chat-msgs");
const div = document.createElement("div");
div.className = `chat-msg chat-msg-${from}`;
div.textContent = text;
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
function handleChatKey(e) {
if (e.key === "Enter") {
e.preventDefault();
sendChat();
}
}
async function sendChat() {
const input = document.getElementById("chat-input");
const btn = document.getElementById("chat-send-btn");
const msg = input.value.trim();
if (!msg) return;
appendChatMsg("user", msg);
input.value = "";
btnLoad(btn, "…");
try {
dbg("→ Chat", CHAT_URL);
const res = await apiFetch(CHAT_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: msg,
sessionId: chatId,
email: currentEmail,
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
dbg("← Chat", json);
const first = Array.isArray(json) ? json[0] : json;
const reply =
typeof json === "string"
? json
: (first &&
(first.output ||
first.message ||
first.reply ||
first.response ||
first.text)) ||
JSON.stringify(json);
appendChatMsg("bot", reply);
} catch (err) {
appendChatMsg("bot", `Sorry, something went wrong (${err.message}).`);
} finally {
btnReset(btn);
document.getElementById("chat-input").focus();
}
}
async function sendReferral() {
const nameInput = document.getElementById("name");
const input = document.getElementById("refer-email");
const btn = document.getElementById("refer-send-btn");
const name = nameInput.value.trim();
const email = input.value.trim();
if (!name) {
toast("Please enter your friend\u2019s name.", "warning");
return;
}
if (!email) {
toast("Please enter your friend\u2019s email.", "warning");
return;
}
if (!isValidEmail(email)) {
toast("Please enter a valid email address.", "warning");
return;
}
btnLoad(btn, "Sending\u2026");
try {
dbg("\u2192 Referral", REFERRAL_URL);
const res = await apiFetch(REFERRAL_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
referrer: currentEmail,
friend_name: name,
friend_email: email,
uuid: activeUUID,
}),
});
if (!res.ok) throw new Error(`Failed to send invite (HTTP ${res.status}).`);
const json = await res.json().catch(() => null);
dbg("\u2190 Referral", json);
const first = Array.isArray(json) ? json[0] : json;
const result = first && first.result;
const success =
typeof result === "string" &&
result.toLowerCase().includes("invite send");
showReferralResult(
success ? result : "This email address has already been claimed.",
success,
);
if (success) {
input.value = "";
nameInput.value = "";
}
} catch (err) {
toast(err.message, "error");
} finally {
btnReset(btn);
}
}
function showReferralResult(text, success = true) {
let el = document.getElementById("refer-result");
if (!el) {
el = document.createElement("p");
el.id = "refer-result";
el.className = "refer-result";
document
.getElementById("refer-send-btn")
.closest(".refer-input-row")
.insertAdjacentElement("afterend", el);
}
el.textContent = text;
el.style.color = success ? "var(--success)" : "var(--warning)";
}