Live AI Pricing: fetch from P&L manager, auto-update tier cards + form selector, add AI Pricing badge
This commit is contained in:
+73
-1
@@ -92,7 +92,8 @@
|
|||||||
.tier-specs li:last-child{border-bottom:none}
|
.tier-specs li:last-child{border-bottom:none}
|
||||||
.tier-specs li .check{color:var(--green)}
|
.tier-specs li .check{color:var(--green)}
|
||||||
.tier-specs li .x{color:var(--red)}
|
.tier-specs li .x{color:var(--red)}
|
||||||
.tier-card .btn{width:100%;justify-content:center}
|
.tier-card .btn{width:100%;justify-content:center;font-size:.82rem;white-space:nowrap}
|
||||||
|
#aiPricingBadge.loaded{opacity:1!important}
|
||||||
.why-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1.5rem}
|
.why-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1.5rem}
|
||||||
.why-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:2rem}
|
.why-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:2rem}
|
||||||
.why-card .icon{font-size:1.8rem;margin-bottom:1rem}
|
.why-card .icon{font-size:1.8rem;margin-bottom:1rem}
|
||||||
@@ -224,6 +225,16 @@
|
|||||||
<h2 class="section-title"><span class="en-only">Pick a tier. Get a server.</span><span class="fr-only">Choisissez une offre. Recevez un serveur.</span></h2>
|
<h2 class="section-title"><span class="en-only">Pick a tier. Get a server.</span><span class="fr-only">Choisissez une offre. Recevez un serveur.</span></h2>
|
||||||
<p class="section-sub en-only">Four options, from entry-level to redundant storage. All include hardware, on-site installation, and a working server before we leave.</p>
|
<p class="section-sub en-only">Four options, from entry-level to redundant storage. All include hardware, on-site installation, and a working server before we leave.</p>
|
||||||
<p class="section-sub fr-only">Quatre options, de l'entrée de gamme au stockage redondant. Chaque offre inclut le matériel, l'installation sur place et un serveur opérationnel avant notre départ.</p>
|
<p class="section-sub fr-only">Quatre options, de l'entrée de gamme au stockage redondant. Chaque offre inclut le matériel, l'installation sur place et un serveur opérationnel avant notre départ.</p>
|
||||||
|
<div style="display:flex;align-items:center;gap:.75rem;margin-bottom:2rem;flex-wrap:wrap">
|
||||||
|
<span id="aiPricingBadge" style="display:inline-flex;align-items:center;gap:.35rem;font-size:.72rem;padding:.2rem .6rem;border-radius:100px;background:rgba(59,130,246,.1);border:1px solid var(--accent);color:var(--accent);font-weight:500;opacity:.6;transition:opacity .3s">
|
||||||
|
<span style="width:6px;height:6px;border-radius:50%;background:var(--accent);animation:pulse 2s infinite;display:inline-block"></span>
|
||||||
|
<span class="en-only">AI Pricing — live from P&L</span><span class="fr-only">Prix AI — en direct du P&L</span>
|
||||||
|
</span>
|
||||||
|
<span style="font-size:.75rem;color:var(--text-muted)">
|
||||||
|
<span class="en-only">Prices auto-update when you change costs in the P&L manager</span>
|
||||||
|
<span class="fr-only">Les prix se mettent à jour automatiquement depuis le gestionnaire P&L</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="tiers-grid">
|
<div class="tiers-grid">
|
||||||
<div class="tier-card">
|
<div class="tier-card">
|
||||||
<div class="tier-name">Rif</div>
|
<div class="tier-name">Rif</div>
|
||||||
@@ -503,6 +514,67 @@
|
|||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const API_URL = 'http://192.168.1.126:8581';
|
const API_URL = 'http://192.168.1.126:8581';
|
||||||
|
|
||||||
|
// ── Live AI Pricing: fetch from P&L manager via API proxy ──
|
||||||
|
const TIER_PRICE_MAP = {rif: 'Rif', rifplus: 'Rif+', atlas: 'Atlas', atlasplus: 'Atlas+'};
|
||||||
|
let livePrices = {};
|
||||||
|
let pricingLoaded = false;
|
||||||
|
|
||||||
|
async function fetchPricing() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_URL + '/api/public/pricing');
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const data = await resp.json();
|
||||||
|
for (const svc of data.pricing) {
|
||||||
|
livePrices[svc.name] = svc;
|
||||||
|
}
|
||||||
|
pricingLoaded = true;
|
||||||
|
// Update tier card prices
|
||||||
|
document.querySelectorAll('.tier-card .amount').forEach(el => {
|
||||||
|
const card = el.closest('.tier-card');
|
||||||
|
if (!card) return;
|
||||||
|
const name = card.querySelector('.tier-name')?.textContent?.trim().replace('⁺', '+') || '';
|
||||||
|
const price = livePrices[name];
|
||||||
|
if (price) {
|
||||||
|
const isFr = document.documentElement.classList.contains('lang-fr');
|
||||||
|
el.textContent = price.sell_price.toLocaleString() + ' MAD';
|
||||||
|
// Update the "From" label to show monthly
|
||||||
|
const priceLine = el.closest('.tier-price');
|
||||||
|
if (priceLine) {
|
||||||
|
const prefix = priceLine.querySelector('.en-only');
|
||||||
|
const prefixFr = priceLine.querySelector('.fr-only');
|
||||||
|
if (prefix) prefix.textContent = 'From';
|
||||||
|
if (prefixFr) prefixFr.textContent = 'À partir de';
|
||||||
|
}
|
||||||
|
// Change button text to show price
|
||||||
|
const btn = card.querySelector('.btn');
|
||||||
|
if (btn) {
|
||||||
|
btn.textContent = price.sell_price.toLocaleString() + ' MAD/mois';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Update tier-selector pricing labels
|
||||||
|
document.querySelectorAll('.tier-option').forEach(el => {
|
||||||
|
const tierKey = el.dataset.tier;
|
||||||
|
const name = TIER_PRICE_MAP[tierKey];
|
||||||
|
const price = livePrices[name];
|
||||||
|
if (price) {
|
||||||
|
let tag = el.querySelector('.t-tag');
|
||||||
|
if (tag) {
|
||||||
|
tag.textContent = price.sell_price.toLocaleString() + ' MAD/mois';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Show AI Pricing badge
|
||||||
|
document.getElementById('aiPricingBadge')?.classList.add('loaded');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Pricing API unavailable — using defaults');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch pricing immediately and every 60 seconds
|
||||||
|
fetchPricing();
|
||||||
|
setInterval(fetchPricing, 60000);
|
||||||
const totalSteps = 4;
|
const totalSteps = 4;
|
||||||
let currentStep = 1;
|
let currentStep = 1;
|
||||||
let selectedTier = '';
|
let selectedTier = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user