Skip to content

Commit 69d640a

Browse files
committed
Add trial CTA to plugin header
1 parent 73916d7 commit 69d640a

File tree

3 files changed

+333
-52
lines changed

3 files changed

+333
-52
lines changed

admin/css/admin.css

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,83 @@
8585
height: auto;
8686
}
8787

88+
.aeo-header-trial-offer {
89+
display: flex;
90+
flex-direction: column;
91+
gap: 12px;
92+
width: min(100%, 540px);
93+
padding: 18px 20px;
94+
border: 1px solid rgba(15, 118, 110, 0.18);
95+
border-radius: 22px;
96+
background:
97+
radial-gradient(circle at top right, rgba(251, 240, 215, 0.9), transparent 42%),
98+
linear-gradient(180deg, rgba(255,255,255,0.98), rgba(223,244,239,0.82));
99+
box-shadow: 0 16px 30px rgba(31, 42, 51, 0.08);
100+
}
101+
102+
.aeo-header-trial-kicker {
103+
display: inline-flex;
104+
align-items: center;
105+
justify-content: center;
106+
width: max-content;
107+
max-width: 100%;
108+
min-height: 28px;
109+
padding: 0 10px;
110+
border-radius: 999px;
111+
background: var(--aeo-warning-soft);
112+
color: #8a5a12;
113+
font-size: 11px;
114+
font-weight: 700;
115+
letter-spacing: 0.05em;
116+
text-transform: uppercase;
117+
}
118+
119+
.aeo-header-trial-title {
120+
margin: 0;
121+
font-size: 22px;
122+
line-height: 1.15;
123+
color: var(--aeo-ink);
124+
}
125+
126+
.aeo-header-trial-body,
127+
.aeo-header-trial-note {
128+
margin: 0;
129+
font-size: 14px;
130+
line-height: 1.6;
131+
color: #50575e;
132+
}
133+
134+
.aeo-header-trial-note strong {
135+
color: var(--aeo-ink);
136+
}
137+
138+
.aeo-header-trial-actions {
139+
display: flex;
140+
align-items: center;
141+
gap: 10px;
142+
flex-wrap: wrap;
143+
}
144+
145+
.aeo-header-trial-actions .button {
146+
min-height: 40px;
147+
padding-inline: 16px;
148+
}
149+
150+
.aeo-header-trial-offer.is-active {
151+
border-color: rgba(47, 133, 90, 0.24);
152+
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(228,243,232,0.92));
153+
}
154+
155+
.aeo-header-trial-offer.is-exhausted {
156+
border-color: rgba(31, 42, 51, 0.14);
157+
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(238,242,244,0.94));
158+
}
159+
160+
.aeo-header-trial-offer.is-error {
161+
border-color: rgba(196, 61, 61, 0.2);
162+
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(253,231,226,0.92));
163+
}
164+
88165
.aeo-header-side {
89166
display: flex;
90167
flex-direction: column;

admin/js/audit.js

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,10 @@
17811781
return document.getElementById('aeo-header-content-suggestions');
17821782
}
17831783

1784+
function getHeaderTrialOfferMount() {
1785+
return document.getElementById('aeo-header-trial-offer');
1786+
}
1787+
17841788
function buildHeaderRewritePriorityItems(audit) {
17851789
var items = buildRewriteCandidates(audit).map(function (candidate) {
17861790
return {
@@ -1805,6 +1809,22 @@
18051809

18061810
function renderHeaderRewritePriorityCard(audit) {
18071811
if (!audit || !audit.pages_reviewed || !audit.pages_reviewed.length) {
1812+
if (auditUiState.phase === 'error') {
1813+
return ''
1814+
+ '<div class="aeo-header-priority-empty">'
1815+
+ '<strong>Rewrite priorities are not ready yet.</strong>'
1816+
+ '<p>' + esc(auditUiState.message || 'The latest audit could not be loaded. Refresh the page or rerun the audit to try again.') + '</p>'
1817+
+ '</div>';
1818+
}
1819+
1820+
if (auditUiState.phase === 'pending') {
1821+
return ''
1822+
+ '<div class="aeo-header-priority-empty">'
1823+
+ '<strong>The latest audit is still running…</strong>'
1824+
+ '<p>' + esc(auditUiState.message || 'Rewrite priorities appear after the next completed audit snapshot lands.') + '</p>'
1825+
+ '</div>';
1826+
}
1827+
18081828
return ''
18091829
+ '<div class="aeo-header-priority-empty">'
18101830
+ '<strong>Loading rewrite priorities…</strong>'
@@ -2017,7 +2037,72 @@
20172037
mount.innerHTML = renderHeaderContentSuggestionsCard();
20182038
}
20192039

2040+
function renderHeaderTrialOfferCard() {
2041+
var data = getRewriteAvailabilityData() || {};
2042+
var available = typeof data.available === 'number' ? data.available : 0;
2043+
var limit = typeof data.limit === 'number' ? data.limit : 0;
2044+
var starterArticles = data.starterArticles > 0 ? data.starterArticles : 5;
2045+
var starterPrice = data.starterPriceCents > 0 ? formatMoneyCents(data.starterPriceCents) : '$1';
2046+
var planLabel = formatRewritePlanLabel(data.plan, data.planLabel);
2047+
var upgradeUrl = getRewriteUpgradeUrl();
2048+
var cardClass = 'aeo-header-trial-offer';
2049+
var kicker = '$1 Starter Trial';
2050+
var title = 'Checking article trial availability...';
2051+
var body = 'A one-time Stripe Checkout charge can unlock ' + starterArticles + ' AEO article credits for rewrites or new articles in Studio.';
2052+
var note = '';
2053+
var actionHtml = '';
2054+
2055+
if (rewriteAvailabilityState.phase === 'error') {
2056+
cardClass += ' is-error';
2057+
title = 'Starter trial is temporarily unavailable';
2058+
body = rewriteAvailabilityState.message || 'The plugin could not load article trial availability right now.';
2059+
note = 'You can still open Studio to manage billing directly.';
2060+
if (upgradeUrl) {
2061+
actionHtml = '<a href="' + esc(upgradeUrl) + '" class="button button-secondary" target="_blank" rel="noopener">Manage in Studio</a>';
2062+
}
2063+
} else if (available > 0) {
2064+
cardClass += ' is-active';
2065+
kicker = planLabel || 'AEO Article Credits';
2066+
title = available + ' of ' + (limit > 0 ? limit : starterArticles) + ' article credits remaining';
2067+
body = 'Use these credits on full AEO rewrites or brand-new article runs in Studio.';
2068+
note = 'The ' + (planLabel || 'active plan') + ' balance is attached to this connected account.';
2069+
if (upgradeUrl) {
2070+
actionHtml = '<a href="' + esc(upgradeUrl) + '" class="button button-secondary" target="_blank" rel="noopener">Manage in Studio</a>';
2071+
}
2072+
} else if (data.checkoutEnabled && data.starterEligible) {
2073+
title = 'Upgrade for ' + starterPrice + ' and unlock ' + starterArticles + ' AEO articles';
2074+
body = 'Use them on full rewrites or new article creation in Studio. Stripe Checkout securely collects card details for a one-time ' + starterPrice + ' charge.';
2075+
note = 'This starter trial is designed as a fast path out of the free plan.';
2076+
actionHtml = '<button type="button" class="button button-primary aeo-start-rewrite-checkout"' + (rewriteCheckoutState.loading ? ' disabled aria-disabled="true"' : '') + '>' + esc(rewriteCheckoutState.loading ? 'Opening Stripe checkout...' : ('Upgrade for ' + starterPrice)) + '</button>';
2077+
} else {
2078+
cardClass += ' is-exhausted';
2079+
kicker = planLabel || 'Starter Trial';
2080+
title = planLabel === '$1 Trial Plan' ? 'Your $1 trial is exhausted' : 'Starter trial unavailable';
2081+
body = 'Upgrade in Studio to keep generating AEO rewrites and new articles after the trial credits are spent.';
2082+
note = 'Billing runs through Stripe Checkout as a one-time payment for the starter trial.';
2083+
if (upgradeUrl) {
2084+
actionHtml = '<a href="' + esc(upgradeUrl) + '" class="button button-secondary" target="_blank" rel="noopener">Upgrade in Studio</a>';
2085+
}
2086+
}
2087+
2088+
return ''
2089+
+ '<div id="aeo-header-trial-offer" class="' + esc(cardClass) + '">'
2090+
+ '<span class="aeo-header-trial-kicker">' + esc(kicker) + '</span>'
2091+
+ '<h2 class="aeo-header-trial-title">' + esc(title) + '</h2>'
2092+
+ '<p class="aeo-header-trial-body">' + esc(body) + '</p>'
2093+
+ (note ? '<p class="aeo-header-trial-note"><strong>Billing:</strong> ' + esc(note) + '</p>' : '')
2094+
+ (actionHtml ? '<div class="aeo-header-trial-actions">' + actionHtml + '</div>' : '')
2095+
+ '</div>';
2096+
}
2097+
2098+
function renderHeaderTrialOffer() {
2099+
var mount = getHeaderTrialOfferMount();
2100+
if (!mount) return;
2101+
mount.outerHTML = renderHeaderTrialOfferCard();
2102+
}
2103+
20202104
function refreshHeaderInsights() {
2105+
renderHeaderTrialOffer();
20212106
renderHeaderRewritePriority(currentAuditData);
20222107
renderHeaderContentSuggestions();
20232108
}
@@ -2034,6 +2119,10 @@
20342119
var currentAuditData = null;
20352120
var currentDiscoveryPayload = null;
20362121
var currentVisibilityPayload = null;
2122+
var auditUiState = {
2123+
phase: 'idle',
2124+
message: ''
2125+
};
20372126
var visibilityUiState = {
20382127
phase: 'idle',
20392128
message: ''
@@ -2205,14 +2294,14 @@
22052294
actionHtml = '<a href="' + esc(upgradeUrl) + '" class="button button-secondary" target="_blank" rel="noopener">Manage account</a>';
22062295
}
22072296
} else if (available > 0) {
2208-
body = (planLabel ? planLabel + ' active. ' : '') + available + ' rewrite' + (available === 1 ? '' : 's') + ' remaining';
2297+
body = (planLabel ? planLabel + ' active. ' : '') + available + ' AEO article credit' + (available === 1 ? '' : 's') + ' remaining';
22092298
if (limit > 0) body += ' out of ' + limit + '.';
22102299
else body += '.';
22112300
} else if (data.checkoutEnabled && data.starterEligible) {
2212-
body = 'Unlock ' + starterArticles + ' full-article rewrites for ' + starterPrice + '.';
2213-
actionHtml = '<button type="button" class="button button-primary aeo-start-rewrite-checkout"' + (rewriteCheckoutState.loading ? ' disabled aria-disabled="true"' : '') + '>' + esc(rewriteCheckoutState.loading ? 'Opening checkout...' : ('Unlock ' + starterArticles + ' rewrites for ' + starterPrice)) + '</button>';
2301+
body = 'Unlock ' + starterArticles + ' AEO article credits for ' + starterPrice + '. Use them on rewrites or new articles in Studio.';
2302+
actionHtml = '<button type="button" class="button button-primary aeo-start-rewrite-checkout"' + (rewriteCheckoutState.loading ? ' disabled aria-disabled="true"' : '') + '>' + esc(rewriteCheckoutState.loading ? 'Opening checkout...' : ('Upgrade for ' + starterPrice)) + '</button>';
22142303
} else {
2215-
body = (planLabel ? planLabel + '. ' : '') + 'No rewrite tokens remain on this account.';
2304+
body = (planLabel ? planLabel + '. ' : '') + 'No AEO article credits remain on this account.';
22162305
if (upgradeUrl) {
22172306
actionHtml = '<a href="' + esc(upgradeUrl) + '" class="button button-secondary" target="_blank" rel="noopener">Upgrade in Studio</a>';
22182307
}
@@ -4812,6 +4901,9 @@
48124901

48134902
function loadAudit(refresh) {
48144903
if (!currentAuditData) {
4904+
auditUiState.phase = refresh ? 'refreshing' : 'loading';
4905+
auditUiState.message = '';
4906+
refreshHeaderInsights();
48154907
setAuditTabsLoading();
48164908
setSiteAuditPending();
48174909
}
@@ -4826,6 +4918,8 @@
48264918
.then(function (r) { return r.json(); })
48274919
.then(function (res) {
48284920
if (res.success) {
4921+
auditUiState.phase = 'ready';
4922+
auditUiState.message = '';
48294923
stopAuditRetry();
48304924
renderAudit(res.data);
48314925
} else {
@@ -4846,10 +4940,14 @@
48464940
} else {
48474941
showError(msg + ' Showing the last completed audit snapshot.');
48484942
}
4943+
auditUiState.phase = 'ready';
4944+
auditUiState.message = '';
48494945
refreshWorkflowChrome();
48504946
return;
48514947
}
48524948

4949+
auditUiState.phase = code === 'aeocas_no_audit' ? 'pending' : 'error';
4950+
auditUiState.message = msg;
48534951
AUDIT_TAB_IDS.forEach(function (id) {
48544952
var el = document.getElementById('tab-' + id);
48554953
if (!el) return;
@@ -4860,25 +4958,32 @@
48604958
}
48614959
});
48624960
startAuditRetry();
4961+
refreshHeaderInsights();
48634962
refreshWorkflowChrome();
48644963
}
48654964
})
48664965
.catch(function (err) {
4966+
var msg = 'Network error: ' + (err.message || 'Please try again.');
48674967
if (currentAuditData) {
4868-
showError('Network error: ' + (err.message || 'Please try again.') + ' Showing the last completed audit snapshot.');
4968+
showError(msg + ' Showing the last completed audit snapshot.');
4969+
auditUiState.phase = 'ready';
4970+
auditUiState.message = '';
48694971
refreshWorkflowChrome();
48704972
return;
48714973
}
4974+
auditUiState.phase = 'error';
4975+
auditUiState.message = msg;
48724976
AUDIT_TAB_IDS.forEach(function (id) {
48734977
var el = document.getElementById('tab-' + id);
48744978
if (!el) return;
48754979
if (id === 'site-audit') {
48764980
el.innerHTML = renderSiteAuditPending();
48774981
} else {
4878-
el.innerHTML = renderAuditEmpty('Network error: ' + (err.message || 'Please try again.'));
4982+
el.innerHTML = renderAuditEmpty(msg);
48794983
}
48804984
});
48814985
startAuditRetry();
4986+
refreshHeaderInsights();
48824987
refreshWorkflowChrome();
48834988
});
48844989
}
@@ -4931,6 +5036,7 @@
49315036
if (!refresh && !tab.innerHTML.trim()) {
49325037
discoveryUiState.phase = 'loading';
49335038
tab.innerHTML = renderDiscoveryLoading();
5039+
refreshHeaderInsights();
49345040
}
49355041

49365042
var data = new FormData();
@@ -5008,9 +5114,19 @@
50085114
refreshWorkflowChrome();
50095115
}
50105116
})
5011-
.catch(function () {
5012-
// Network error — keep polling silently; the ticker keeps ticking
5013-
// and "Last checked" will drift, making the stall visible.
5117+
.catch(function (err) {
5118+
if (!currentDiscoveryPayload) {
5119+
var msg = 'Network error: ' + ((err && err.message) || 'Please try again.');
5120+
stopDiscoveryPolling();
5121+
stopDiscoveryTicker();
5122+
discoveryUiState.phase = 'error';
5123+
discoveryUiState.status = 'failed';
5124+
discoveryUiState.currentStage = msg;
5125+
currentDiscoveryPayload = { status: 'failed', current_stage: msg };
5126+
tab.innerHTML = '<div class="notice notice-error" style="padding:12px 16px;"><p>' + esc(msg) + '</p></div>';
5127+
}
5128+
refreshHeaderInsights();
5129+
refreshWorkflowChrome();
50145130
});
50155131
}
50165132

0 commit comments

Comments
 (0)