live seat availability: fetch from server webhook, orange/red styling when <10/0 seats, disable sold-out locations
This commit is contained in:
@@ -299,6 +299,8 @@
|
||||
}
|
||||
.pricing-config-select:hover { border-color: var(--accent); }
|
||||
.pricing-config-select:focus { border-color: var(--accent); }
|
||||
.pricing-config-select.low-seats { border-color: var(--warning); box-shadow: 0 0 0 2px rgba(255, 170, 0, 0.2); }
|
||||
.pricing-config-select.no-seats { border-color: var(--danger); box-shadow: 0 0 0 2px rgba(255, 59, 59, 0.2); }
|
||||
.pricing-config-input {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
@@ -363,7 +365,7 @@
|
||||
<option value="yearly">Yearly</option>
|
||||
</select>
|
||||
<select class="pricing-config-select" id="pricing-location">
|
||||
<option value="france">France</option>
|
||||
<option value="">Loading locations...</option>
|
||||
</select>
|
||||
<input type="email" class="pricing-config-input" id="pricing-email" placeholder="Email" autocomplete="email" />
|
||||
<input type="text" class="pricing-config-input" id="pricing-coupon" placeholder="Coupon code" />
|
||||
@@ -539,6 +541,51 @@
|
||||
<script>
|
||||
// ── Signup Webhook URLs (matching app.derez.ai) ──────────
|
||||
const ORDER_WEBHOOK = "https://n8n.derez.ai/webhook/payment";
|
||||
const SERVER_WEBHOOK = "https://n8n.derez.ai/webhook/server";
|
||||
|
||||
async function initServerLocations() {
|
||||
const sel = document.getElementById('pricing-location');
|
||||
if (!sel) return;
|
||||
try {
|
||||
const res = await fetch(SERVER_WEBHOOK);
|
||||
const data = await res.json();
|
||||
const servers = Array.isArray(data) ? data : [data];
|
||||
sel.innerHTML = '';
|
||||
servers.forEach(function(s) {
|
||||
const server = s.server || '';
|
||||
const available = parseInt(s['Seats Available'], 10) || 0;
|
||||
const label = server.charAt(0).toUpperCase() + server.slice(1);
|
||||
const opt = document.createElement('option');
|
||||
opt.value = server.toLowerCase().replace(/\s+/g, '-');
|
||||
if (available <= 0) {
|
||||
opt.textContent = label + ' (sold out)';
|
||||
opt.disabled = true;
|
||||
opt.style.color = '#ff4444';
|
||||
opt.style.fontWeight = '600';
|
||||
sel.classList.remove('low-seats');
|
||||
sel.classList.add('no-seats');
|
||||
} else if (available < 10) {
|
||||
opt.textContent = label + ' (available ' + available + ')';
|
||||
opt.style.color = '#ff9900';
|
||||
opt.style.fontWeight = '600';
|
||||
sel.classList.add('low-seats');
|
||||
sel.classList.remove('no-seats');
|
||||
} else {
|
||||
opt.textContent = label + ' (available ' + available + ')';
|
||||
opt.style.color = '';
|
||||
opt.style.fontWeight = '';
|
||||
sel.classList.remove('low-seats', 'no-seats');
|
||||
}
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
if (!sel.options[i].disabled) { sel.selectedIndex = i; break; }
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load server locations:', err);
|
||||
sel.innerHTML = '<option value="france" style="color:#ff9900;font-weight:600">France (available ?)</option>';
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Period / billing update ──────────────────────────── */
|
||||
function updatePeriodDisplay() {
|
||||
@@ -553,6 +600,7 @@
|
||||
}
|
||||
document.getElementById('pricing-period').addEventListener('change', updatePeriodDisplay);
|
||||
updatePeriodDisplay();
|
||||
initServerLocations();
|
||||
|
||||
/* ── Capture UTM params from URL ──────────────────────── */
|
||||
(function () {
|
||||
|
||||
@@ -972,6 +972,14 @@ section {
|
||||
.pricing-config-select:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.pricing-config-select.low-seats {
|
||||
border-color: var(--warning);
|
||||
box-shadow: 0 0 0 2px rgba(255, 170, 0, 0.2);
|
||||
}
|
||||
.pricing-config-select.no-seats {
|
||||
border-color: var(--danger);
|
||||
box-shadow: 0 0 0 2px rgba(255, 59, 59, 0.2);
|
||||
}
|
||||
.pricing-config-input {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
|
||||
@@ -287,6 +287,7 @@ document
|
||||
.getElementById("pricing-period")
|
||||
.addEventListener("change", updatePeriodDisplay);
|
||||
updatePeriodDisplay(); // fire on load so yearly bonuses show by default
|
||||
initServerLocations(); // fetch live server availability
|
||||
|
||||
/* ── Capture UTM params from URL ──────────────────────────── */
|
||||
(function () {
|
||||
@@ -638,6 +639,69 @@ async function chatSend() {
|
||||
});
|
||||
})();
|
||||
|
||||
/* ── Server Locations (live seat availability) ──────── */
|
||||
const SERVER_WEBHOOK = "https://n8n.derez.ai/webhook/server";
|
||||
|
||||
async function initServerLocations() {
|
||||
const sel = document.getElementById("pricing-location");
|
||||
if (!sel) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(SERVER_WEBHOOK);
|
||||
const data = await res.json();
|
||||
const servers = Array.isArray(data) ? data : [data];
|
||||
|
||||
// Clear existing options
|
||||
sel.innerHTML = "";
|
||||
|
||||
servers.forEach(function (s) {
|
||||
const server = s.server || "";
|
||||
const available = parseInt(s["Seats Available"], 10) || 0;
|
||||
const label = server.charAt(0).toUpperCase() + server.slice(1);
|
||||
const opt = document.createElement("option");
|
||||
opt.value = server.toLowerCase().replace(/\s+/g, "-");
|
||||
|
||||
if (available <= 0) {
|
||||
// No seats — red, disabled, cannot select
|
||||
opt.textContent = label + " (sold out)";
|
||||
opt.disabled = true;
|
||||
opt.style.color = "#ff4444";
|
||||
opt.style.fontWeight = "600";
|
||||
sel.classList.remove("low-seats");
|
||||
sel.classList.add("no-seats");
|
||||
} else if (available < 10) {
|
||||
// Low seats — orange with count
|
||||
opt.textContent = label + " (available " + available + ")";
|
||||
opt.style.color = "#ff9900";
|
||||
opt.style.fontWeight = "600";
|
||||
sel.classList.add("low-seats");
|
||||
sel.classList.remove("no-seats");
|
||||
} else {
|
||||
// Plenty of seats
|
||||
opt.textContent = label + " (available " + available + ")";
|
||||
opt.style.color = "";
|
||||
opt.style.fontWeight = "";
|
||||
sel.classList.remove("low-seats", "no-seats");
|
||||
}
|
||||
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
|
||||
// Default to first available option
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
if (!sel.options[i].disabled) {
|
||||
sel.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load server locations:", err);
|
||||
// Fallback: leave the placeholder option
|
||||
sel.innerHTML =
|
||||
'<option value="france" style="color:#ff9900;font-weight:600">France (available ?)</option>';
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Coupon Badges ──────────────────────────────────── */
|
||||
const COUPONS = {
|
||||
// 499
|
||||
|
||||
+14
-16
@@ -57,15 +57,15 @@
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"text": "60 YouTube creators collected on Hold",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"text": "Need business emails for top 20",
|
||||
"text": "60 YouTube creators collected — campaign paused until end of July",
|
||||
"ok": false
|
||||
},
|
||||
{
|
||||
"text": "Affiliate program TBD",
|
||||
"text": "Focus now: Friends & Family battle-testing first",
|
||||
"ok": false
|
||||
},
|
||||
{
|
||||
"text": "Influencer affiliate program to be designed after F&F validation",
|
||||
"ok": false
|
||||
}
|
||||
]
|
||||
@@ -92,7 +92,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"state_of_play": "Day 6 of prelaunch (Sunday). Major milestone: 1st agent sold! Traffic: 28 unique visitors / 249 pageviews across 5 days, 7 countries. Unblocked: 12 real YT creator emails found via YouTube about page scrape (NetworkChuck, Wes Roth, Metics Media, Nick Puru, Nate Herk, Bart Slodyczka, Zach Babiarz, Astroquirk, FuturMinds, DaviddTech, Nuno Tavares, CodeHead). Still blocked: Search Console (needs Oliver TXT record), 46 YT creators still need emails. New features: Interactive Architecture Finder, feedback form, YouTube video integration with UTM tracking, countdown screenshot proof, video chip on homepage. Brand: derez.ai now consistently lowercase.",
|
||||
"state_of_play": "Day 7 — Influencer campaign on hold until end of July. Full focus on Friends & Family battle-testing. 1 agent sold, 28 visitors across 5 days, 7 countries. Battle-proofing the system with real users before scaling to influencers. 39 coworking space leads + 6 e-commerce leads on Hold, ready for F&F outreach.",
|
||||
"comms": {
|
||||
"inbox": {
|
||||
"total": 0,
|
||||
@@ -105,14 +105,12 @@
|
||||
"alerts": 0
|
||||
},
|
||||
"recommendations": [
|
||||
"Search Console submission still #1 blocker \u2014 needs Oliver to add TXT record for domain verification.",
|
||||
"12 YT creator emails found! Top targets: NetworkChuck (chuck@networkchuck.com), Wes Roth (wesroth@smoothmedia.co), Metics Media (contact@meticsmedia.com). Ready for cold outreach.",
|
||||
"Cold outreach templates and coupons ready. First batch can go to the 12 creators with real emails.",
|
||||
"1 agent sold! First social proof \u2014 publish testimonial/social proof tweet.",
|
||||
"Submit to BetaList and AlternativeTo directories \u2014 requires browser session (blocked from headless curl).",
|
||||
"Pitch guest post to 5 AI/DevOps blogs, set up HARO/Qwoted alerts.",
|
||||
"Goal for D7: mid-campaign review, send first batch influencer emails, polish Product Hunt listing."
|
||||
"INFLUENCER CAMPAIGN PAUSED until end of July — focus on F&F battle-testing first",
|
||||
"Friends & Family list goes live Monday — start onboarding and collect feedback",
|
||||
"Battle-proof the system: fix issues as they come up during F&F usage",
|
||||
"End of July: evaluate F&F results, then re-activate influencer campaign with tested system",
|
||||
"1 agent sold already — good sign for F&F conversion expectations"
|
||||
],
|
||||
"day": 6,
|
||||
"notes": "Day 6 auto-update at 09:00 PYST \u2014 1 agent sold milestone"
|
||||
"day": 7,
|
||||
"notes": "Day 7 — Influencer campaign paused until end of July. Full focus on F&F battle-testing."
|
||||
}
|
||||
+1
-1
@@ -801,7 +801,7 @@
|
||||
<option value="yearly">Yearly</option>
|
||||
</select>
|
||||
<select class="pricing-config-select" id="pricing-location">
|
||||
<option value="france">France</option>
|
||||
<option value="">Loading locations...</option>
|
||||
</select>
|
||||
<input
|
||||
type="email"
|
||||
|
||||
Reference in New Issue
Block a user