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');