406 lines
22 KiB
HTML
406 lines
22 KiB
HTML
<!-- Pricing Section Component -->
|
|
<section id="pricing" class="py-5">
|
|
<div class="container">
|
|
<div class="text-center mb-5">
|
|
<h2 class="display-5 fw-bold mb-3">Simple, Transparent Pricing</h2>
|
|
<p class="lead text-muted">Choose the plan that fits your organization's needs</p>
|
|
</div>
|
|
|
|
{% set pricing_plans = PricingPlan.get_active_plans() %}
|
|
{% if pricing_plans %}
|
|
<!-- Debug info -->
|
|
<div style="display: none;" id="pricing-debug">
|
|
<h4>Debug: Pricing Plans Found</h4>
|
|
{% for plan in pricing_plans %}
|
|
<div>
|
|
Plan: {{ plan.name }} (ID: {{ plan.id }})
|
|
- Monthly Price ID: {{ plan.stripe_monthly_price_id or 'None' }}
|
|
- Annual Price ID: {{ plan.stripe_annual_price_id or 'None' }}
|
|
- Is Custom: {{ plan.is_custom }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="row g-4 justify-content-center">
|
|
{% for plan in pricing_plans %}
|
|
<div class="col-md-3">
|
|
<div class="card pricing-card h-100 d-flex flex-column {% if plan.is_popular %}border-primary position-relative{% endif %}"
|
|
{% if plan.is_popular %}style="border: 3px solid var(--primary-color) !important;"{% endif %}>
|
|
|
|
{% if plan.is_popular %}
|
|
<div class="position-absolute top-0 start-0" style="z-index: 10;">
|
|
<span class="badge px-3 py-2" style="background: var(--primary-color); color: white; font-size: 0.8rem; font-weight: 600; border-radius: 0 0 15px 0; margin-top: 0; border-top-left-radius: 10px;">
|
|
Most Popular
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="card-body text-center p-5 d-flex flex-column">
|
|
<div class="flex-grow-1">
|
|
<h3 class="card-title">{{ plan.name }}</h3>
|
|
|
|
{% if plan.is_custom %}
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">Custom</div>
|
|
{% else %}
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">
|
|
<span class="monthly-price">€{{ "%.0f"|format(plan.monthly_price) }}</span>
|
|
<span class="annual-price" style="display: none;">€{{ "%.0f"|format(plan.annual_price) }}</span>
|
|
<span class="fs-6 text-muted">/month</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if plan.description %}
|
|
<p class="text-muted mb-3">{{ plan.description }}</p>
|
|
{% endif %}
|
|
|
|
<ul class="list-unstyled mb-4">
|
|
{% for feature in plan.features %}
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>{{ feature }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Dynamic Payment Button -->
|
|
{% if plan.is_custom %}
|
|
<a href="{{ contact_url }}" class="btn {% if plan.is_popular %}btn-primary{% else %}btn-outline-primary{% endif %} btn-lg w-100 mt-auto px-4 py-3">
|
|
{{ plan.button_text }}
|
|
</a>
|
|
{% else %}
|
|
{% if plan.stripe_monthly_price_id or plan.stripe_annual_price_id %}
|
|
<button class="btn {% if plan.is_popular %}btn-primary{% else %}btn-outline-primary{% endif %} btn-lg w-100 mt-auto px-4 py-3 checkout-button"
|
|
data-plan-id="{{ plan.id }}"
|
|
data-monthly-product-id="{{ plan.stripe_monthly_price_id or '' }}"
|
|
data-annual-product-id="{{ plan.stripe_annual_price_id or '' }}"
|
|
data-plan-name="{{ plan.name }}"
|
|
data-monthly-price="{{ plan.monthly_price or 0 }}"
|
|
data-annual-price="{{ plan.annual_price or 0 }}">
|
|
{{ plan.button_text }}
|
|
</button>
|
|
{% else %}
|
|
<a href="{{ contact_url }}" class="btn {% if plan.is_popular %}btn-primary{% else %}btn-outline-primary{% endif %} btn-lg w-100 mt-auto px-4 py-3">
|
|
{{ plan.button_text }}
|
|
</a>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Billing Toggle - Only show if there are non-custom plans -->
|
|
{% set has_non_custom_plans = pricing_plans | selectattr('is_custom', 'equalto', false) | list | length > 0 %}
|
|
{% if has_non_custom_plans %}
|
|
<div class="d-flex justify-content-center align-items-center mt-4 mb-3">
|
|
<span class="me-3">Monthly</span>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="annualBilling" style="width: 3rem; height: 1.5rem; background-color: var(--border-color); border-color: var(--border-color);">
|
|
<label class="form-check-label" for="annualBilling"></label>
|
|
</div>
|
|
<span class="ms-3">Annual <span class="badge text-white px-2 py-1 ms-1" style="background: var(--primary-color); font-size: 0.75rem;">Save 20%</span></span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<!-- Fallback to default pricing if no plans are configured -->
|
|
<div class="row g-4 justify-content-center">
|
|
<div class="col-md-3">
|
|
<div class="card pricing-card h-100 d-flex flex-column">
|
|
<div class="card-body text-center p-5 d-flex flex-column">
|
|
<div class="flex-grow-1">
|
|
<h3 class="card-title">Starter</h3>
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">
|
|
<span class="monthly-price">€29</span>
|
|
<span class="annual-price" style="display: none;">€23</span>
|
|
<span class="fs-6 text-muted price-period">/month</span>
|
|
</div>
|
|
<ul class="list-unstyled mb-4">
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 5 rooms</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 10 conversations</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>10GB storage</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 10 managers</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Email support</li>
|
|
</ul>
|
|
</div>
|
|
<a href="{{ contact_url }}" class="btn btn-outline-primary btn-lg w-100 mt-auto px-4 py-3">Get Started</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card pricing-card h-100 border-primary d-flex flex-column position-relative" style="border: 3px solid var(--primary-color) !important;">
|
|
<div class="position-absolute top-0 start-0" style="z-index: 10;">
|
|
<span class="badge px-3 py-2" style="background: var(--primary-color); color: white; font-size: 0.8rem; font-weight: 600; border-radius: 0 0 15px 0; margin-top: 0; border-top-left-radius: 10px;">
|
|
Most Popular
|
|
</span>
|
|
</div>
|
|
<div class="card-body text-center p-5 d-flex flex-column">
|
|
<div class="flex-grow-1">
|
|
<h3 class="card-title">Professional</h3>
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">
|
|
<span class="monthly-price">€99</span>
|
|
<span class="annual-price" style="display: none;">€79</span>
|
|
<span class="fs-6 text-muted price-period">/month</span>
|
|
</div>
|
|
<ul class="list-unstyled mb-4">
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 25 rooms</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 50 conversations</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>100GB storage</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 50 managers</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Priority support</li>
|
|
</ul>
|
|
</div>
|
|
<a href="{{ contact_url }}" class="btn btn-primary btn-lg w-100 mt-auto px-4 py-3">Get Started</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card pricing-card h-100 d-flex flex-column">
|
|
<div class="card-body text-center p-5 d-flex flex-column">
|
|
<div class="flex-grow-1">
|
|
<h3 class="card-title">Enterprise</h3>
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">
|
|
<span class="monthly-price">€299</span>
|
|
<span class="annual-price" style="display: none;">€239</span>
|
|
<span class="fs-6 text-muted price-period">/month</span>
|
|
</div>
|
|
<ul class="list-unstyled mb-4">
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 100 rooms</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 200 conversations</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>500GB storage</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Up to 200 managers</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>24/7 dedicated support</li>
|
|
</ul>
|
|
</div>
|
|
<a href="{{ contact_url }}" class="btn btn-outline-primary btn-lg w-100 mt-auto px-4 py-3">Get Started</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card pricing-card h-100 d-flex flex-column">
|
|
<div class="card-body text-center p-5 d-flex flex-column">
|
|
<div class="flex-grow-1">
|
|
<h3 class="card-title">Custom</h3>
|
|
<div class="display-4 fw-bold mb-3" style="color: var(--primary-color);">Custom</div>
|
|
<ul class="list-unstyled mb-4">
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Unlimited rooms</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Unlimited conversations</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Unlimited storage</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Unlimited users</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Custom integrations</li>
|
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>Dedicated account manager</li>
|
|
</ul>
|
|
</div>
|
|
<a href="{{ contact_url }}" class="btn btn-outline-primary btn-lg w-100 mt-auto px-4 py-3">Contact Sales</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing Toggle -->
|
|
<div class="d-flex justify-content-center align-items-center mt-4 mb-3">
|
|
<span class="me-3">Monthly</span>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="annualBilling" style="width: 3rem; height: 1.5rem; background-color: var(--border-color); border-color: var(--border-color);">
|
|
<label class="form-check-label" for="annualBilling"></label>
|
|
</div>
|
|
<span class="ms-3">Annual <span class="badge text-white px-2 py-1 ms-1" style="background: var(--primary-color); font-size: 0.75rem;">Save 20%</span></span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Debug: Log pricing plans info
|
|
console.log('=== PRICING DEBUG INFO ===');
|
|
const checkoutButtons = document.querySelectorAll('.checkout-button');
|
|
console.log('Found checkout buttons:', checkoutButtons.length);
|
|
|
|
checkoutButtons.forEach((button, index) => {
|
|
console.log(`Button ${index + 1}:`, {
|
|
planId: button.getAttribute('data-plan-id'),
|
|
monthlyProductId: button.getAttribute('data-monthly-product-id'),
|
|
annualProductId: button.getAttribute('data-annual-product-id'),
|
|
planName: button.getAttribute('data-plan-name'),
|
|
monthlyPrice: button.getAttribute('data-monthly-price'),
|
|
annualPrice: button.getAttribute('data-annual-price')
|
|
});
|
|
});
|
|
|
|
// Show debug info if needed (uncomment to show)
|
|
// document.getElementById('pricing-debug').style.display = 'block';
|
|
|
|
const billingToggle = document.getElementById('annualBilling');
|
|
if (!billingToggle) return;
|
|
|
|
const monthlyPrices = document.querySelectorAll('.monthly-price');
|
|
const annualPrices = document.querySelectorAll('.annual-price');
|
|
const pricePeriods = document.querySelectorAll('.price-period');
|
|
|
|
// Add CSS for switch styling
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.form-check-input:checked {
|
|
background-color: var(--primary-color) !important;
|
|
border-color: var(--primary-color) !important;
|
|
}
|
|
.form-check-input:focus {
|
|
box-shadow: 0 0 0 0.25rem rgba(var(--primary-color-rgb), 0.25) !important;
|
|
}
|
|
|
|
.price-number {
|
|
display: inline-block;
|
|
transition: all 0.3s ease;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
// Function to animate number counting
|
|
function animateNumber(element, startValue, endValue, duration = 500) {
|
|
const start = performance.now();
|
|
const difference = endValue - startValue;
|
|
|
|
function updateNumber(currentTime) {
|
|
const elapsed = currentTime - start;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
// Easing function for smooth animation
|
|
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
|
const currentValue = startValue + (difference * easeOutQuart);
|
|
|
|
element.textContent = '€' + Math.round(currentValue);
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(updateNumber);
|
|
} else {
|
|
// Ensure the final value is correct
|
|
element.textContent = '€' + endValue;
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(updateNumber);
|
|
}
|
|
|
|
// Function to handle Stripe checkout
|
|
async function handleCheckout(planId, billingCycle) {
|
|
console.log('handleCheckout called with:', { planId, billingCycle });
|
|
|
|
try {
|
|
const requestBody = {
|
|
plan_id: planId,
|
|
billing_cycle: billingCycle
|
|
};
|
|
console.log('Sending request to /api/create-checkout-session with body:', requestBody);
|
|
|
|
const response = await fetch('/api/create-checkout-session', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(requestBody)
|
|
});
|
|
|
|
console.log('Response status:', response.status);
|
|
console.log('Response ok:', response.ok);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('Response error text:', errorText);
|
|
throw new Error(`Failed to create checkout session: ${response.status} ${errorText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('Response data:', data);
|
|
|
|
if (data.checkout_url) {
|
|
console.log('Redirecting to checkout URL:', data.checkout_url);
|
|
window.location.href = data.checkout_url;
|
|
} else {
|
|
console.error('No checkout URL received in response');
|
|
alert('Failed to create checkout session. Please try again.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Checkout error:', error);
|
|
alert('Failed to start checkout. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Add click handlers for checkout buttons
|
|
document.querySelectorAll('.checkout-button').forEach(button => {
|
|
console.log('Adding click handler to checkout button:', button);
|
|
button.addEventListener('click', function() {
|
|
console.log('Checkout button clicked!');
|
|
console.log('Button data attributes:', {
|
|
planId: this.getAttribute('data-plan-id'),
|
|
monthlyProductId: this.getAttribute('data-monthly-product-id'),
|
|
annualProductId: this.getAttribute('data-annual-product-id'),
|
|
planName: this.getAttribute('data-plan-name'),
|
|
monthlyPrice: this.getAttribute('data-monthly-price'),
|
|
annualPrice: this.getAttribute('data-annual-price')
|
|
});
|
|
|
|
const planId = this.getAttribute('data-plan-id');
|
|
const monthlyProductId = this.getAttribute('data-monthly-product-id');
|
|
const annualProductId = this.getAttribute('data-annual-product-id');
|
|
const planName = this.getAttribute('data-plan-name');
|
|
const monthlyPrice = parseFloat(this.getAttribute('data-monthly-price'));
|
|
const annualPrice = parseFloat(this.getAttribute('data-annual-price'));
|
|
|
|
// Determine which billing cycle to use based on billing toggle
|
|
const isAnnual = billingToggle.checked;
|
|
const billingCycle = isAnnual ? 'annual' : 'monthly';
|
|
|
|
console.log('Billing toggle state:', { isAnnual, billingCycle });
|
|
console.log('Plan ID:', planId);
|
|
|
|
if (planId) {
|
|
console.log('Calling handleCheckout with planId and billingCycle');
|
|
handleCheckout(planId, billingCycle);
|
|
} else {
|
|
console.log('No plan ID found, redirecting to contact form');
|
|
// Fallback to contact form if no plan configured
|
|
window.location.href = '{{ contact_url }}';
|
|
}
|
|
});
|
|
});
|
|
|
|
billingToggle.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
// Switch to annual prices with animation
|
|
monthlyPrices.forEach((price, index) => {
|
|
const monthlyValue = parseInt(price.textContent.replace('€', ''));
|
|
const annualValue = parseInt(annualPrices[index].textContent.replace('€', ''));
|
|
|
|
// Store the original monthly value for later use
|
|
price.setAttribute('data-original-monthly', monthlyValue);
|
|
|
|
// Simply animate the number change
|
|
animateNumber(price, monthlyValue, annualValue);
|
|
});
|
|
|
|
// Update price periods to show "/year"
|
|
document.querySelectorAll('.fs-6.text-muted, .price-period').forEach(period => {
|
|
if (period.textContent.includes('/month')) {
|
|
period.textContent = '/year';
|
|
}
|
|
});
|
|
} else {
|
|
// Switch to monthly prices with animation
|
|
monthlyPrices.forEach((price, index) => {
|
|
const currentValue = parseInt(price.textContent.replace('€', ''));
|
|
const originalMonthlyValue = parseInt(price.getAttribute('data-original-monthly'));
|
|
|
|
// Simply animate the number change back to monthly
|
|
animateNumber(price, currentValue, originalMonthlyValue);
|
|
});
|
|
|
|
// Update price periods to show "/month"
|
|
document.querySelectorAll('.fs-6.text-muted, .price-period').forEach(period => {
|
|
if (period.textContent.includes('/year')) {
|
|
period.textContent = '/month';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script> |