diff --git a/hub/services/static/js/price-calculator/dom-manager.js b/hub/services/static/js/price-calculator/dom-manager.js
index 2e3b9e8..9c08813 100644
--- a/hub/services/static/js/price-calculator/dom-manager.js
+++ b/hub/services/static/js/price-calculator/dom-manager.js
@@ -53,12 +53,16 @@ class DOMManager {
this.elements.serviceLevelGroup = document.getElementById('serviceLevelGroup');
}
- // Get element by key
+ // Get element by key with error handling
get(key) {
- return this.elements[key];
+ const element = this.elements[key];
+ if (!element && key !== 'addonsContainer') {
+ console.warn(`DOM element '${key}' not found`);
+ }
+ return element;
}
- // Check if element exists
+ // Check if element exists and is valid
has(key) {
return this.elements[key] && this.elements[key] !== null;
}
diff --git a/hub/services/static/js/price-calculator/order-manager.js b/hub/services/static/js/price-calculator/order-manager.js
index 99ea5a8..7db484d 100644
--- a/hub/services/static/js/price-calculator/order-manager.js
+++ b/hub/services/static/js/price-calculator/order-manager.js
@@ -46,7 +46,15 @@ class OrderManager {
messageField.value = configMessage;
}
- // Store configuration details in hidden field
+ // Find and fill alternative message field if the first one doesn't exist
+ if (!messageField) {
+ const altMessageField = document.querySelector('textarea[name="message"]');
+ if (altMessageField) {
+ altMessageField.value = configMessage;
+ }
+ }
+
+ // Store configuration details in hidden field if it exists
const detailsField = document.querySelector('#order-form input[name="details"]');
if (detailsField) {
detailsField.value = JSON.stringify({
diff --git a/hub/services/static/js/price-calculator/plan-manager.js b/hub/services/static/js/price-calculator/plan-manager.js
index 9abdc0f..8a7452e 100644
--- a/hub/services/static/js/price-calculator/plan-manager.js
+++ b/hub/services/static/js/price-calculator/plan-manager.js
@@ -64,7 +64,11 @@ class PlanManager {
if (!planSelect) return;
const serviceLevel = domManager.getSelectedServiceLevel();
- if (!serviceLevel) return;
+ if (!serviceLevel) {
+ // Clear dropdown if no service level is selected
+ planSelect.innerHTML = '';
+ return;
+ }
// Clear existing options
planSelect.innerHTML = '';
@@ -72,6 +76,11 @@ class PlanManager {
// Get plans for the selected service level
const availablePlans = this.pricingDataManager.getPlansForServiceLevel(serviceLevel);
+ if (!availablePlans || availablePlans.length === 0) {
+ planSelect.innerHTML = '';
+ return;
+ }
+
// Add plans to dropdown
availablePlans.forEach(plan => {
const option = document.createElement('option');
diff --git a/hub/services/static/js/price-calculator/price-calculator.js b/hub/services/static/js/price-calculator/price-calculator.js
index 542c48f..85c1150 100644
--- a/hub/services/static/js/price-calculator/price-calculator.js
+++ b/hub/services/static/js/price-calculator/price-calculator.js
@@ -4,17 +4,27 @@
*/
class PriceCalculator {
constructor() {
- // Initialize managers
- this.domManager = new DOMManager();
- this.currentOffering = this.extractOfferingFromURL();
- this.pricingDataManager = new PricingDataManager(this.currentOffering);
- this.planManager = new PlanManager(this.pricingDataManager);
- this.addonManager = new AddonManager(this.pricingDataManager);
- this.uiManager = new UIManager();
- this.orderManager = new OrderManager();
+ try {
+ // Initialize managers
+ this.domManager = new DOMManager();
+ this.currentOffering = this.extractOfferingFromURL();
- // Initialize the calculator
- this.init();
+ if (!this.currentOffering) {
+ throw new Error('Unable to extract offering information from URL');
+ }
+
+ this.pricingDataManager = new PricingDataManager(this.currentOffering);
+ this.planManager = new PlanManager(this.pricingDataManager);
+ this.addonManager = new AddonManager(this.pricingDataManager);
+ this.uiManager = new UIManager();
+ this.orderManager = new OrderManager();
+
+ // Initialize the calculator
+ this.init();
+ } catch (error) {
+ console.error('Error initializing PriceCalculator:', error);
+ this.showInitializationError(error.message);
+ }
}
// Extract offering info from URL
@@ -41,11 +51,24 @@ class PriceCalculator {
this.orderManager.setupOrderButton(this.domManager);
this.updateCalculator();
} else {
- console.warn('No current offering found, calculator not initialized');
+ throw new Error('No current offering found');
}
} catch (error) {
console.error('Error initializing price calculator:', error);
- this.uiManager.showError(this.domManager, 'Failed to load pricing information');
+ this.showInitializationError(error.message);
+ }
+ }
+
+ // Show initialization error to user
+ showInitializationError(message) {
+ const planMatchStatus = this.domManager?.get('planMatchStatus');
+ if (planMatchStatus) {
+ planMatchStatus.innerHTML = `
+
+ Failed to load pricing calculator: ${message}
+ `;
+ planMatchStatus.className = 'alert alert-danger mb-3';
+ planMatchStatus.style.display = 'block';
}
}
@@ -130,6 +153,23 @@ class PriceCalculator {
window.addEventListener('addon-changed', () => {
this.updatePricing();
});
+
+ // Service level change listener
+ const serviceLevelInputs = this.domManager.get('serviceLevelInputs');
+ if (serviceLevelInputs) {
+ serviceLevelInputs.forEach(input => {
+ input.addEventListener('change', () => {
+ // Update plan dropdown for new service level
+ this.planManager.populatePlanDropdown(this.domManager);
+
+ // Update addons for new service level
+ this.addonManager.updateAddons(this.domManager);
+
+ // Update pricing
+ this.updatePricing();
+ });
+ });
+ }
}
// Update calculator (initial setup)
diff --git a/hub/services/static/js/price-calculator/pricing-data-manager.js b/hub/services/static/js/price-calculator/pricing-data-manager.js
index 147086d..411d7ef 100644
--- a/hub/services/static/js/price-calculator/pricing-data-manager.js
+++ b/hub/services/static/js/price-calculator/pricing-data-manager.js
@@ -13,7 +13,8 @@ class PricingDataManager {
// Load pricing data from API endpoint
async loadPricingData() {
try {
- const response = await fetch(`/offering/${this.currentOffering.provider_slug}/${this.currentOffering.service_slug}/?pricing=json`);
+ const url = `/offering/${this.currentOffering.provider_slug}/${this.currentOffering.service_slug}/?pricing=json`;
+ const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to load pricing data: ${response.status} ${response.statusText}`);
@@ -21,8 +22,17 @@ class PricingDataManager {
const data = await response.json();
+ if (!data || typeof data !== 'object') {
+ throw new Error('Invalid pricing data received from server');
+ }
+
this.pricingData = data.pricing || data;
+ // Validate that we have usable pricing data
+ if (!this.pricingData || Object.keys(this.pricingData).length === 0) {
+ throw new Error('No pricing data available for this offering');
+ }
+
// Extract addons data from the plans - addons are embedded in each plan
this.extractAddonsData();
diff --git a/hub/services/static/js/price-calculator/ui-manager.js b/hub/services/static/js/price-calculator/ui-manager.js
index b87e10d..f37b44b 100644
--- a/hub/services/static/js/price-calculator/ui-manager.js
+++ b/hub/services/static/js/price-calculator/ui-manager.js
@@ -89,18 +89,18 @@ class UIManager {
const managedServiceIncludesContainer = domManager.get('managedServiceIncludesContainer');
const managedServiceIncludes = domManager.get('managedServiceIncludes');
const managedServiceToggleButton = domManager.get('managedServiceToggleButton');
-
+
if (managedServiceIncludesContainer) {
// Clear existing content
managedServiceIncludesContainer.innerHTML = '';
// Show/hide the entire managed service includes section based on mandatory addons
const hasMandatoryAddons = mandatoryAddons && mandatoryAddons.length > 0;
-
+
if (managedServiceIncludes) {
managedServiceIncludes.style.display = hasMandatoryAddons ? 'block' : 'none';
}
-
+
if (managedServiceToggleButton) {
managedServiceToggleButton.style.display = hasMandatoryAddons ? 'inline-block' : 'none';
}
@@ -218,7 +218,7 @@ class UIManager {
// Update the serviceLevelInputs reference
domManager.elements.serviceLevelInputs = document.querySelectorAll('input[name="serviceLevel"]');
-
+
// Set up event listeners for the dynamically created service level inputs
this.setupServiceLevelEventListeners(domManager, pricingDataManager);
}
diff --git a/hub/services/templates/services/offering_detail.html b/hub/services/templates/services/offering_detail.html
index 8c33b34..6e995b5 100644
--- a/hub/services/templates/services/offering_detail.html
+++ b/hub/services/templates/services/offering_detail.html
@@ -9,7 +9,49 @@
{% block extra_js %}
{% if debug %}
-
+{% compress js inline %}
+
+
+
+
+
+
+
+
+{% endcompress %}
{% else %}
{% compress js %}
@@ -25,12 +67,18 @@
document.addEventListener('DOMContentLoaded', () => {
// Check if we're on a page that needs the price calculator
if (document.getElementById('cpuRange')) {
- window.priceCalculator = new PriceCalculator();
+ try {
+ window.priceCalculator = new PriceCalculator();
+ } catch (error) {
+ console.error('Failed to initialize price calculator:', error);
+ }
}
});
// Global function for traditional plan selection (used by template buttons)
function selectPlan(element) {
+ if (!element) return;
+
const planId = element.getAttribute('data-plan-id');
const planName = element.getAttribute('data-plan-name');