userIdc2024's picture
Update frontend/script.js
12c2987 verified
// ===== Configuration =====
const API_BASE = "";
// ===== DOM Elements =====
const form = document.getElementById("researchForm");
const categoryInput = document.getElementById("productCategory");
const descriptionInput = document.getElementById("productDescription");
const submitBtn = document.getElementById("submitBtn");
const btnText = submitBtn.querySelector(".btn-text");
const btnLoader = submitBtn.querySelector(".btn-loader");
const errorBanner = document.getElementById("errorBanner");
const resultsDiv = document.getElementById("results");
const toggleBtns = document.querySelectorAll(".toggle-btn");
// Multi-select elements
const multiselect = document.getElementById("audienceMultiselect");
const selectedContainer = document.getElementById("selectedAudiences");
const dropdown = document.getElementById("audienceDropdown");
const searchInput = document.getElementById("audienceSearch");
const optionsContainer = document.getElementById("audienceOptions");
let selectedMethod = "gpt";
let selectedAudiences = [];
let allAudiences = [];
// ===== Load Target Audiences =====
async function loadAudiences() {
try {
const res = await fetch(`${API_BASE}/api/target-audiences`);
if (!res.ok) throw new Error("Failed to load audiences");
const data = await res.json();
allAudiences = data.audiences;
renderOptions();
renderSelected();
} catch (err) {
console.error("Could not load audiences:", err);
selectedContainer.innerHTML =
'<span class="multiselect-placeholder">⚠ Could not load — is the backend running?</span>';
}
}
// ===== Multi-select Logic (UNCHANGED) =====
function renderOptions(filter = "") {
const filterLower = filter.toLowerCase();
const filtered = allAudiences.filter((a) =>
a.toLowerCase().includes(filterLower)
);
optionsContainer.innerHTML = filtered
.map((a) => {
const isSelected = selectedAudiences.includes(a);
return `
<div class="multiselect-option ${isSelected ? "selected" : ""}" data-value="${escapeAttr(a)}">
<span class="check">${isSelected ? "✓" : ""}</span>
<span>${escapeHtml(a)}</span>
</div>
`;
})
.join("");
optionsContainer.querySelectorAll(".multiselect-option").forEach((opt) => {
opt.addEventListener("click", () => {
const val = opt.dataset.value;
if (selectedAudiences.includes(val)) {
selectedAudiences = selectedAudiences.filter((a) => a !== val);
} else {
selectedAudiences.push(val);
}
renderOptions(searchInput.value);
renderSelected();
});
});
}
function renderSelected() {
if (selectedAudiences.length === 0) {
selectedContainer.innerHTML =
'<span class="multiselect-placeholder">Select target audiences…</span>';
return;
}
selectedContainer.innerHTML = selectedAudiences
.map(
(a) => `
<span class="multiselect-tag">
${escapeHtml(a)}
<span class="multiselect-tag-remove" data-value="${escapeAttr(a)}">×</span>
</span>
`
)
.join("");
selectedContainer.querySelectorAll(".multiselect-tag-remove").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const val = btn.dataset.value;
selectedAudiences = selectedAudiences.filter((a) => a !== val);
renderOptions(searchInput.value);
renderSelected();
});
});
}
selectedContainer.addEventListener("click", () => {
dropdown.classList.toggle("hidden");
});
document.addEventListener("click", (e) => {
if (!multiselect.contains(e.target)) {
dropdown.classList.add("hidden");
}
});
searchInput.addEventListener("input", () => {
renderOptions(searchInput.value);
});
dropdown.addEventListener("click", (e) => {
e.stopPropagation();
});
// ===== Method Toggle =====
toggleBtns.forEach((btn) => {
btn.addEventListener("click", () => {
toggleBtns.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
selectedMethod = btn.dataset.method;
});
});
// ===== Form Submit =====
form.addEventListener("submit", async (e) => {
e.preventDefault();
hideError();
hideResults();
const payload = {
target_audience: selectedAudiences,
product_category: categoryInput.value.trim(),
product_description: descriptionInput.value.trim(),
method: selectedMethod,
};
if (
payload.target_audience.length === 0 ||
!payload.product_category ||
!payload.product_description
) {
showError(
"Please fill in all fields and select at least one target audience."
);
return;
}
setLoading(true);
try {
const res = await fetch(`${API_BASE}/api/research`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error("Server error");
const data = await res.json();
renderResults(data.results, selectedMethod);
} catch (err) {
showError(err.message || "Something went wrong.");
} finally {
setLoading(false);
}
});
// =====================================================
// ✅ NEW RESULTS (VERTICAL NESTED COLLAPSIBLE)
// =====================================================
function renderResults(audienceResults, method) {
const badge =
method === "gpt"
? `<span class="results-badge gpt">GPT</span>`
: `<span class="results-badge claude">Claude</span>`;
let html = `
<div class="results-header">
<h2 class="results-title">Results</h2>
${badge}
</div>
<div class="audience-stack">
`;
audienceResults.forEach((group, groupIndex) => {
html += `
<div class="audience-card ${groupIndex === 0 ? "open" : ""}">
<div class="audience-card-header">
<div class="audience-header-left">
<span>👥</span>
<h3>${escapeHtml(group.target_audience)}</h3>
</div>
<span class="audience-chevron">▾</span>
</div>
<div class="audience-card-body">
`;
group.output.forEach((item, idx) => {
html += `
<div class="trigger-card ${idx === 0 && groupIndex === 0 ? "open" : ""}">
<div class="trigger-header">
<div>
<span class="trigger-label">Trigger ${idx + 1}</span>
<h4 class="trigger-name">${escapeHtml(item.phsychologyTriggers)}</h4>
</div>
<span class="trigger-chevron">▾</span>
</div>
<div class="trigger-body">
<div class="trigger-section">
<p class="section-title">Ad Angles</p>
<ul class="section-list">
${item.angles.map((a) => `<li>${escapeHtml(a)}</li>`).join("")}
</ul>
</div>
<div class="trigger-section">
<p class="section-title">Ad Concepts</p>
<ul class="section-list">
${item.concepts.map((c) => `<li>${escapeHtml(c)}</li>`).join("")}
</ul>
</div>
</div>
</div>
`;
});
html += `
</div>
</div>
`;
});
html += `</div>`;
resultsDiv.innerHTML = html;
resultsDiv.classList.remove("hidden");
attachAccordionEvents();
}
// ===== Accordion Logic =====
function attachAccordionEvents() {
document.querySelectorAll(".audience-card-header").forEach((header) => {
header.addEventListener("click", () => {
const card = header.parentElement;
card.classList.toggle("open");
});
});
document.querySelectorAll(".trigger-header").forEach((header) => {
header.addEventListener("click", () => {
const card = header.parentElement;
// Close siblings
const siblings = card.parentElement.querySelectorAll(".trigger-card");
siblings.forEach((s) => {
if (s !== card) s.classList.remove("open");
});
card.classList.toggle("open");
});
});
}
// ===== Helpers =====
function setLoading(isLoading) {
submitBtn.disabled = isLoading;
btnText.classList.toggle("hidden", isLoading);
btnLoader.classList.toggle("hidden", !isLoading);
}
function showError(msg) {
errorBanner.textContent = msg;
errorBanner.classList.remove("hidden");
}
function hideError() {
errorBanner.classList.add("hidden");
}
function hideResults() {
resultsDiv.classList.add("hidden");
}
function escapeHtml(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function escapeAttr(str) {
return str.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
}
loadAudiences();