saving payment data

This commit is contained in:
2025-06-24 12:08:13 +02:00
parent 782be6bd38
commit d7f5809771
4 changed files with 314 additions and 2 deletions

View File

@@ -49,6 +49,54 @@
.badge.bg-orange:hover {
background-color: #e55a00 !important;
}
/* Pricing tier selection styles */
.pricing-card {
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.pricing-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-color: var(--primary-color);
}
.pricing-card.selected {
border-color: var(--primary-color);
background-color: rgba(22, 118, 123, 0.05);
box-shadow: 0 4px 12px rgba(22, 118, 123, 0.2);
}
.pricing-card.selected::after {
content: '✓';
position: absolute;
top: 10px;
right: 10px;
background-color: var(--primary-color);
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
}
.pricing-card.border-primary {
border-color: var(--primary-color) !important;
}
.quota-info {
font-size: 0.75rem;
}
.features {
text-align: left;
}
</style>
{% endblock %}
@@ -246,6 +294,10 @@
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Hidden fields for pricing tier selection -->
<input type="hidden" id="selectedPricingTierId" value="">
<input type="hidden" id="selectedPricingTierName" value="">
<!-- Steps Navigation -->
<div class="d-flex justify-content-between mb-4">
<div class="step-item active" data-step="1">
@@ -270,6 +322,10 @@
</div>
<div class="step-item" data-step="6">
<div class="step-circle">6</div>
<div class="step-label">Pricing Tier</div>
</div>
<div class="step-item" data-step="7">
<div class="step-circle">7</div>
<div class="step-label">Launch</div>
</div>
</div>
@@ -479,8 +535,30 @@
</div>
</div>
<!-- Step 6 -->
<!-- Step 6: Pricing Tier Selection -->
<div class="step-pane" id="step6">
<div class="step-content">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-4">Select Pricing Tier</h5>
<p class="text-muted mb-4">Choose the pricing tier that best fits your needs. This will determine the resource limits for your instance.</p>
<div id="pricingTiersContainer" class="row">
<!-- Pricing tiers will be loaded here -->
<div class="col-12 text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading pricing tiers...</span>
</div>
<p class="mt-2">Loading available pricing tiers...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Step 7 -->
<div class="step-pane" id="step7">
<div class="text-center">
<i class="fas fa-rocket fa-4x mb-4" style="color: var(--primary-color);"></i>
<h4>Ready to Launch!</h4>
@@ -494,6 +572,7 @@
<p><strong>Repository:</strong> <span id="reviewRepo"></span></p>
<p><strong>Branch:</strong> <span id="reviewBranch"></span></p>
<p><strong>Company:</strong> <span id="reviewCompany"></span></p>
<p><strong>Pricing Tier:</strong> <span id="reviewPricingTier"></span></p>
</div>
<div class="col-md-6">
<p><strong>Port:</strong> <span id="reviewPort"></span></p>
@@ -723,7 +802,7 @@ let launchStepsModal;
let currentStep = 1;
// Update the total number of steps
const totalSteps = 6;
const totalSteps = 7;
document.addEventListener('DOMContentLoaded', function() {
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
@@ -1650,6 +1729,11 @@ function updateStepDisplay() {
if (currentStep === 4) {
getNextAvailablePort();
}
// If we're on step 6, load pricing tiers
if (currentStep === 6) {
loadPricingTiers();
}
}
function nextStep() {
@@ -1662,6 +1746,9 @@ function nextStep() {
if (currentStep === 4 && !validateStep4()) {
return;
}
if (currentStep === 6 && !validateStep6()) {
return;
}
if (currentStep < totalSteps) {
currentStep++;
@@ -2066,6 +2153,7 @@ function updateReviewSection() {
const webAddresses = Array.from(document.querySelectorAll('.web-address'))
.map(input => input.value)
.join(', ');
const pricingTier = document.getElementById('selectedPricingTierName').value;
// Update the review section
document.getElementById('reviewRepo').textContent = repo;
@@ -2073,6 +2161,7 @@ function updateReviewSection() {
document.getElementById('reviewCompany').textContent = company;
document.getElementById('reviewPort').textContent = port;
document.getElementById('reviewWebAddresses').textContent = webAddresses;
document.getElementById('reviewPricingTier').textContent = pricingTier || 'Not selected';
}
// Function to launch the instance
@@ -2099,6 +2188,10 @@ function launchInstance() {
colors: {
primary: document.getElementById('primaryColor').value,
secondary: document.getElementById('secondaryColor').value
},
pricingTier: {
id: document.getElementById('selectedPricingTierId').value,
name: document.getElementById('selectedPricingTierName').value
}
};
@@ -2240,5 +2333,119 @@ async function refreshLatestVersion() {
console.log('Manual refresh of latest version requested');
await fetchLatestVersion();
}
// Function to load pricing tiers
async function loadPricingTiers() {
const container = document.getElementById('pricingTiersContainer');
try {
const response = await fetch('/api/admin/pricing-plans', {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
}
});
if (!response.ok) {
throw new Error('Failed to load pricing tiers');
}
const data = await response.json();
if (data.plans && data.plans.length > 0) {
container.innerHTML = data.plans.map(plan => `
<div class="col-md-6 col-lg-4 mb-3">
<div class="card pricing-card h-100 ${plan.is_popular ? 'border-primary' : ''}"
onclick="selectPricingTier(${plan.id}, '${plan.name}')">
<div class="card-body text-center">
${plan.is_popular ? '<div class="badge bg-primary mb-2">Most Popular</div>' : ''}
<h5 class="card-title">${plan.name}</h5>
${plan.description ? `<p class="text-muted small mb-3">${plan.description}</p>` : ''}
<div class="pricing mb-3">
${plan.is_custom ?
'<span class="h4 text-primary">Custom Pricing</span>' :
`<span class="h4 text-primary">€${plan.monthly_price}</span><span class="text-muted">/month</span>`
}
</div>
<div class="quota-info small text-muted mb-3">
<div class="row">
<div class="col-6">
<i class="fas fa-door-open me-1"></i>${plan.format_quota_display.room_quota}<br>
<i class="fas fa-comments me-1"></i>${plan.format_quota_display.conversation_quota}<br>
<i class="fas fa-hdd me-1"></i>${plan.format_quota_display.storage_quota_gb}
</div>
<div class="col-6">
<i class="fas fa-user-tie me-1"></i>${plan.format_quota_display.manager_quota}<br>
<i class="fas fa-user-shield me-1"></i>${plan.format_quota_display.admin_quota}
</div>
</div>
</div>
<div class="features small">
${plan.features.slice(0, 3).map(feature => `<div>✓ ${feature}</div>`).join('')}
${plan.features.length > 3 ? `<div class="text-muted">+${plan.features.length - 3} more features</div>` : ''}
</div>
</div>
</div>
</div>
`).join('');
} else {
container.innerHTML = `
<div class="col-12 text-center">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
No pricing tiers available. Please contact your administrator.
</div>
</div>
`;
}
} catch (error) {
console.error('Error loading pricing tiers:', error);
container.innerHTML = `
<div class="col-12 text-center">
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
Error loading pricing tiers: ${error.message}
</div>
</div>
`;
}
}
// Function to select a pricing tier
function selectPricingTier(planId, planName) {
// Remove selection from all cards
document.querySelectorAll('.pricing-card').forEach(card => {
card.classList.remove('selected');
});
// Add selection to clicked card
event.currentTarget.classList.add('selected');
// Store the selection
document.getElementById('selectedPricingTierId').value = planId;
document.getElementById('selectedPricingTierName').value = planName;
// Enable next button if not already enabled
const nextButton = document.querySelector('#launchStepsFooter .btn-primary');
if (nextButton.disabled) {
nextButton.disabled = false;
}
}
// Function to validate step 6 (pricing tier selection)
function validateStep6() {
const selectedTierId = document.getElementById('selectedPricingTierId').value;
if (!selectedTierId) {
alert('Please select a pricing tier before proceeding.');
return false;
}
return true;
}
</script>
{% endblock %}