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:hover { border-color: var(--accent); }
|
||||||
.pricing-config-select:focus { 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 {
|
.pricing-config-input {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@@ -363,7 +365,7 @@
|
|||||||
<option value="yearly">Yearly</option>
|
<option value="yearly">Yearly</option>
|
||||||
</select>
|
</select>
|
||||||
<select class="pricing-config-select" id="pricing-location">
|
<select class="pricing-config-select" id="pricing-location">
|
||||||
<option value="france">France</option>
|
<option value="">Loading locations...</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="email" class="pricing-config-input" id="pricing-email" placeholder="Email" autocomplete="email" />
|
<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" />
|
<input type="text" class="pricing-config-input" id="pricing-coupon" placeholder="Coupon code" />
|
||||||
@@ -539,6 +541,51 @@
|
|||||||
<script>
|
<script>
|
||||||
// ── Signup Webhook URLs (matching app.derez.ai) ──────────
|
// ── Signup Webhook URLs (matching app.derez.ai) ──────────
|
||||||
const ORDER_WEBHOOK = "https://n8n.derez.ai/webhook/payment";
|
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 ──────────────────────────── */
|
/* ── Period / billing update ──────────────────────────── */
|
||||||
function updatePeriodDisplay() {
|
function updatePeriodDisplay() {
|
||||||
@@ -553,6 +600,7 @@
|
|||||||
}
|
}
|
||||||
document.getElementById('pricing-period').addEventListener('change', updatePeriodDisplay);
|
document.getElementById('pricing-period').addEventListener('change', updatePeriodDisplay);
|
||||||
updatePeriodDisplay();
|
updatePeriodDisplay();
|
||||||
|
initServerLocations();
|
||||||
|
|
||||||
/* ── Capture UTM params from URL ──────────────────────── */
|
/* ── Capture UTM params from URL ──────────────────────── */
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
@@ -972,6 +972,14 @@ section {
|
|||||||
.pricing-config-select:focus {
|
.pricing-config-select:focus {
|
||||||
border-color: var(--accent);
|
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 {
|
.pricing-config-input {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ document
|
|||||||
.getElementById("pricing-period")
|
.getElementById("pricing-period")
|
||||||
.addEventListener("change", updatePeriodDisplay);
|
.addEventListener("change", updatePeriodDisplay);
|
||||||
updatePeriodDisplay(); // fire on load so yearly bonuses show by default
|
updatePeriodDisplay(); // fire on load so yearly bonuses show by default
|
||||||
|
initServerLocations(); // fetch live server availability
|
||||||
|
|
||||||
/* ── Capture UTM params from URL ──────────────────────────── */
|
/* ── Capture UTM params from URL ──────────────────────────── */
|
||||||
(function () {
|
(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 ──────────────────────────────────── */
|
/* ── Coupon Badges ──────────────────────────────────── */
|
||||||
const COUPONS = {
|
const COUPONS = {
|
||||||
// 499
|
// 499
|
||||||
|
|||||||
+14
-16
@@ -57,15 +57,15 @@
|
|||||||
],
|
],
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"text": "60 YouTube creators collected on Hold",
|
"text": "60 YouTube creators collected — campaign paused until end of July",
|
||||||
"ok": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Need business emails for top 20",
|
|
||||||
"ok": false
|
"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
|
"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": {
|
"comms": {
|
||||||
"inbox": {
|
"inbox": {
|
||||||
"total": 0,
|
"total": 0,
|
||||||
@@ -105,14 +105,12 @@
|
|||||||
"alerts": 0
|
"alerts": 0
|
||||||
},
|
},
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"Search Console submission still #1 blocker \u2014 needs Oliver to add TXT record for domain verification.",
|
"INFLUENCER CAMPAIGN PAUSED until end of July — focus on F&F battle-testing first",
|
||||||
"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.",
|
"Friends & Family list goes live Monday — start onboarding and collect feedback",
|
||||||
"Cold outreach templates and coupons ready. First batch can go to the 12 creators with real emails.",
|
"Battle-proof the system: fix issues as they come up during F&F usage",
|
||||||
"1 agent sold! First social proof \u2014 publish testimonial/social proof tweet.",
|
"End of July: evaluate F&F results, then re-activate influencer campaign with tested system",
|
||||||
"Submit to BetaList and AlternativeTo directories \u2014 requires browser session (blocked from headless curl).",
|
"1 agent sold already — good sign for F&F conversion expectations"
|
||||||
"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."
|
|
||||||
],
|
],
|
||||||
"day": 6,
|
"day": 7,
|
||||||
"notes": "Day 6 auto-update at 09:00 PYST \u2014 1 agent sold milestone"
|
"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>
|
<option value="yearly">Yearly</option>
|
||||||
</select>
|
</select>
|
||||||
<select class="pricing-config-select" id="pricing-location">
|
<select class="pricing-config-select" id="pricing-location">
|
||||||
<option value="france">France</option>
|
<option value="">Loading locations...</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
|
|||||||
Reference in New Issue
Block a user