Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/member-dash-brand-properties-nudge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add a setup-alert nudge on the member dashboard for publisher/adtech org admins when brand.json has zero properties. Dismisses with a 7-day localStorage snooze so it resurfaces if properties stay empty.
40 changes: 35 additions & 5 deletions server/public/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -2071,6 +2071,11 @@ <h3>Join the Agentic Advertising Organization</h3>
// Get dismissed alerts from session storage
const dismissedAlerts = JSON.parse(sessionStorage.getItem('dismissedSetupAlerts') || '[]');

// Brand-properties nudge uses a localStorage timestamp for 7-day snooze (keyed per org)
const brandPropsDismissedAt = localStorage.getItem(`propertyNudgeDismissedAt_${org.id}`);
const brandPropsSnoozed = brandPropsDismissedAt &&
(Date.now() - parseInt(brandPropsDismissedAt, 10)) < (7 * 24 * 60 * 60 * 1000);

// Fetch member profile to check for missing items
try {
const response = await fetch(`/api/member-profiles/org/${org.id}`);
Expand Down Expand Up @@ -2130,6 +2135,25 @@ <h3>Join the Agentic Advertising Organization</h3>
});
}

// Nudge publisher/adtech org admins to populate brand.json properties when empty.
// Empty properties = downstream discovery gap (brand→property catalog, ad-agents.json delegation).
if (!isPersonalAccount
&& ['admin', 'owner'].includes(org.role)
&& ['publisher', 'adtech'].includes(org.billing?.company_type)
&& profile.primary_brand_domain
&& profile.resolved_brand
&& (profile.resolved_brand.property_count ?? 0) === 0
&& !brandPropsSnoozed) {
alerts.push({
id: 'missing-brand-properties',
icon: '🏗️',
title: 'List your owned properties',
message: `List the publishers and properties ${org.name ?? 'your organization'} owns or represents — this powers brand-aware discovery and ad-agents.json delegation.`,
actionLabel: 'Add properties',
actionUrl: `/brand-builder?domain=${encodeURIComponent(profile.primary_brand_domain)}#properties`
});
}

// Check for pending join requests (admins only)
try {
const pendingResponse = await fetch(`/api/organizations/${org.id}/join-requests`);
Expand Down Expand Up @@ -2189,14 +2213,20 @@ <h4 class="setup-alert-title">${escapeHtml(alert.title)}</h4>
}

function dismissSetupAlert(alertId) {
const dismissedAlerts = JSON.parse(sessionStorage.getItem('dismissedSetupAlerts') || '[]');
if (!dismissedAlerts.includes(alertId)) {
dismissedAlerts.push(alertId);
sessionStorage.setItem('dismissedSetupAlerts', JSON.stringify(dismissedAlerts));
if (alertId === 'missing-brand-properties') {
// currentOrg is set to org right before renderSetupAlerts is called; org switches rebuild
// the alerts DOM entirely, so currentOrg.id always matches the rendered card's org here.
localStorage.setItem(`propertyNudgeDismissedAt_${currentOrg?.id}`, Date.now().toString());
} else {
const dismissedAlerts = JSON.parse(sessionStorage.getItem('dismissedSetupAlerts') || '[]');
if (!dismissedAlerts.includes(alertId)) {
dismissedAlerts.push(alertId);
sessionStorage.setItem('dismissedSetupAlerts', JSON.stringify(dismissedAlerts));
}
}

// Remove the alert from the DOM
const alertEl = document.querySelector(`.setup-alert[data-alert-id="${alertId}"]`);
const alertEl = document.querySelector(`.setup-alert[data-alert-id="${CSS.escape(alertId)}"]`);
if (alertEl) {
alertEl.remove();
}
Expand Down
Loading