UNPKG

besper-frontend-site-dev-0935

Version:

Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment

1,047 lines (925 loc) 33.5 kB
/** * PowerPages Integration Utility * Optimized integration layer that consolidates all PowerPages logic into the npm package * Reduces PowerPages template complexity and provides a cleaner interface */ import { b_esper_site } from '../besper-site-functions.js'; import TemplateLoaderService from '../services/TemplateLoaderService.js'; /** * PowerPages configuration object that can be passed from liquid templates * @typedef {Object} PowerPagesConfig * @property {string} pageId - The page to render (e.g., 'manage-workspace', 'contact-us') * @property {Object} user - PowerPages user object (from {% if user %}) * @property {string} user.id - User ID * @property {string} user.contactid - Contact ID * @property {string} language - Language code from {{ language.code }} * @property {string} environment - Environment from {{ settings.environment }} * @property {string} branch - Branch from {{ settings.bsp_branch }} * @property {Object} settings - Additional PowerPages settings * @property {boolean} useApimOperators - Whether to enable APIM operators * @property {string} containerId - Container ID (defaults to 'besper-site-container') */ /** * Universal PowerPages integration function * Handles ALL authentication logic, token management, DOM ready state, and page initialization * This moves ALL the logic from PowerPages templates into the npm package * * OPTIMIZED FOR RELIABILITY: * - Immediate UI rendering with skeleton loading * - Robust authentication token handling * - Automatic environment detection * - Comprehensive error recovery * - Promise-based token acquisition * - Graceful fallbacks for all failure modes * * Usage in PowerPages templates (8 lines total): * <script> * window.besperPageIntegration('manage-workspace', { * user: {% if user %}{ id: "{{ user.id }}", contactid: "{{ user.contactid }}" }{% else %}null{% endif %}, * language: "{{ language.code }}", * environment: "{{ settings.environment }}", * branch: "{{ settings.bsp_branch }}" * }); * </script> * * @param {string} pageId - Page to render * @param {PowerPagesConfig} config - PowerPages configuration * @returns {Promise<boolean>} Success status */ export async function initializePowerPagesIntegration(pageId, config = {}) { try { console.log( `[PowerPages] [INIT] Initializing optimized integration for page: ${pageId}` ); // Extract configuration with intelligent defaults // Note: environment and branch are now auto-detected from npm package const { user = null, language = 'en', useApimOperators = true, containerId = 'besper-site-container', debug = false, settings = {}, } = config; // Environment detection is now handled by TemplateLoaderService const isDebugMode = debug || window.location.hostname.includes('localhost'); if (isDebugMode) { console.log( `[PowerPages] 🔧 Debug mode enabled - User: ${user ? 'authenticated' : 'anonymous'}` ); } // PRIORITY 1: Immediate DOM handling and UI rendering await handleDOMReadyState(); // PRIORITY 2: Prepare authentication data using original token generation only const authData = await prepareRobustAuthenticationData(user, isDebugMode); // PRIORITY 3: Prepare comprehensive page options // Environment and branch are auto-detected from npm package configuration const options = { language, useApimOperators, containerId, debug: isDebugMode, powerPagesMode: true, // Flag to indicate PowerPages integration useDirectMapping: config.useDirectMapping || false, // Enable direct mapping if specified requestPath: config.requestPath, // Pass through request path for mapping ...settings, }; // PRIORITY 4: Load and initialize the page with error recovery const success = await loadPageWithErrorRecovery(pageId, authData, options); if (success) { console.log( `[PowerPages] [SUCCESS] Successfully initialized ${pageId} with optimized integration` ); // PRIORITY 5: Deferred PowerPages-specific initialization (non-blocking) setTimeout(() => { initializePowerPagesSpecificFeatures(pageId, authData, options); }, 0); } else { console.error( `[PowerPages] [ERROR] Failed to initialize ${pageId} - showing error UI` ); } return success; } catch (error) { console.error('[PowerPages] 💥 Critical integration error:', error); // Show user-friendly error message with recovery options showPowerPagesError(pageId, error); return false; } } /** * Handle DOM ready state for immediate UI rendering * Ensures page renders immediately regardless of DOM state * @returns {Promise<void>} */ async function handleDOMReadyState() { return new Promise(resolve => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => resolve()); } else { // DOM is already ready resolve(); } }); } /** * Auto-detect page ID from the current URL pathname * Maps PowerPages URL patterns to npm site page IDs * @returns {string|null} Detected page ID or null if not mappable */ function detectPageIdFromUrl() { if (typeof window === 'undefined') return null; const pathname = window.location.pathname.toLowerCase(); // PowerPages URL patterns to page ID mapping const urlMappings = { // Exact matches first '/manage-workspace': 'manage-workspace', '/workspace-management': 'manage-workspace', '/contact-us': 'contact-us', '/account-management': 'account-management', '/user-management': 'user-management-new', '/bot-management': 'bot-management-new', '/cost-pool': 'cost-pool-management', '/cost-pools': 'cost-pool-management', '/pricing': 'pricing', '/home': 'home', '/profile': 'profile', '/notifications': 'notifications', '/support-tickets': 'support-tickets', '/help': 'help', '/about': 'about-us', '/partners': 'partners', '/case-studies': 'case-studies', '/get-started': 'get-started', '/subscription': 'subscription', '/my-bots': 'my-bots', '/workspace': 'workspace', '/workbench': 'workbench', '/users': 'users', '/invite-user': 'invite-user', '/manage-user': 'manage-user', '/technical-insights': 'technical-insights', '/implementation-guide': 'implementation-guide', '/product-purchasing': 'product-purchasing', '/upcoming': 'upcoming', '/demo': 'demo', }; // Check exact matches first if (urlMappings[pathname]) { return urlMappings[pathname]; } // Check for partial matches (e.g., "/manage-workspace/details" -> "manage-workspace") for (const [urlPattern, pageId] of Object.entries(urlMappings)) { if ( pathname.startsWith(urlPattern + '/') || pathname.startsWith(urlPattern + '?') ) { return pageId; } } // Try to extract from common PowerPages patterns const pathSegments = pathname .split('/') .filter(segment => segment.length > 0); if (pathSegments.length > 0) { const firstSegment = pathSegments[0]; // Common patterns if (firstSegment.includes('workspace')) return 'manage-workspace'; if (firstSegment.includes('contact')) return 'contact-us'; if (firstSegment.includes('account')) return 'account-management'; if (firstSegment.includes('user')) return 'user-management-new'; if (firstSegment.includes('bot')) return 'bot-management-new'; if (firstSegment.includes('cost')) return 'cost-pool-management'; if (firstSegment.includes('notification')) return 'notifications'; if (firstSegment.includes('ticket')) return 'support-tickets'; // Handle specific PowerPages naming conventions if (firstSegment.match(/^[a-z-]+$/)) { // Convert dash-case to our page ID format return firstSegment; } } // Default fallback return null; } /** * Auto-detect page integration using direct URL mapping (no API calls) * Maps PowerPages request.path directly to storage directories * @param {PowerPagesConfig} config - PowerPages configuration * @param {string} config.requestPath - The PowerPages request.path (e.g., "/en/account-management/") * @returns {Promise<boolean>} Success status */ // Import DirectUrlMappingService statically to avoid code splitting import DirectUrlMappingService from '../services/DirectUrlMappingService.js'; export async function autoDetectPageIntegration(config = {}) { try { const urlMapper = new DirectUrlMappingService(); // Use provided request.path or fallback to current URL const requestPath = config.requestPath || window.location.pathname; // Map request path to storage directory const mapping = urlMapper.mapRequestPath(requestPath); console.log( `[PowerPages] 🎯 Direct URL mapping: ${requestPath}${mapping.storageDir} (language: ${mapping.language})` ); // Use the mapped storage directory as page ID const pageId = mapping.storageDir; // Add language to config for proper asset loading const enhancedConfig = { ...config, language: mapping.language, requestPath, partialUrl: mapping.partialUrl, useDirectMapping: true, }; // Initialize with mapped page ID return await initializePowerPagesIntegration(pageId, enhancedConfig); } catch (error) { console.error( '[PowerPages] [ERROR] Error in direct URL mapping integration:', error ); // Fallback to URL-based detection console.log('[PowerPages] [LOADING] Falling back to URL-based detection'); return await legacyAutoDetectPageIntegration(config); } } /** * Legacy auto-detect page integration (fallback method) * @param {PowerPagesConfig} config - PowerPages configuration * @returns {Promise<boolean>} Success status */ async function legacyAutoDetectPageIntegration(config = {}) { try { // Try to auto-detect page ID from URL const detectedPageId = detectPageIdFromUrl(); if (!detectedPageId) { console.warn( '[PowerPages] [WARN] Could not auto-detect page ID from URL:', window.location.pathname ); // Fallback to a generic page or show selection UI const fallbackPageId = config.fallbackPageId || 'home'; console.log( `[PowerPages] [LOADING] Using fallback page: ${fallbackPageId}` ); return await initializePowerPagesIntegration(fallbackPageId, config); } console.log( `[PowerPages] 🎯 Auto-detected page ID: ${detectedPageId} from URL: ${window.location.pathname}` ); // Initialize with detected page ID // Environment and branch are auto-detected from npm package return await initializePowerPagesIntegration(detectedPageId, config); } catch (error) { console.error('[PowerPages] 💥 Auto-detection failed:', error); // Ultimate fallback return await initializePowerPagesIntegration('home', config); } } /** * Prepare authentication data from PowerPages user object with robust error handling * Handles all authentication token logic internally using original token generation only * @param {Object|null} user - PowerPages user object * @param {boolean} isDebugMode - Whether debug logging is enabled * @returns {Promise<Object>} Prepared authentication data */ async function prepareRobustAuthenticationData(user, isDebugMode = false) { if (!user) { if (isDebugMode) { console.log('[PowerPages] 🔓 No user provided - unauthenticated mode'); } return { authData: {} }; } if (isDebugMode) { console.log( '[PowerPages] 🔐 User authenticated - preparing auth data with original token generation' ); } // Use original token generation only - no fallbacks ever (per user requirement) try { const authToken = await getTokenFromGlobalAuth(); if (isDebugMode) { console.log( '[PowerPages] [SUCCESS] Authentication token acquired successfully' ); } return { authData: { token: authToken, userId: user.id, contactId: user.contactid, isAuthenticated: true, }, useApimOperators: true, }; } catch (error) { // If token generation fails, it fails - be honest about it console.error( '[PowerPages] [ERROR] Token generation failed:', error.message ); throw new Error(`Authentication failed: ${error.message}`); } } /** * Power Pages Simple Token Manager * Auto-fetches and maintains token for immediate use * Based on the user's requested implementation pattern */ function setupPowerPagesTokenManager() { // Skip if already set up if (window.auth && typeof window.auth.getToken === 'function') { return; } // Configuration const CLIENT_ID = '59b6aeda-4e80-4268-87f1-3706911adacc'; const TOKEN_ENDPOINT = '/_services/auth/token'; // Token storage let _token = null; let _tokenExpiry = null; let _tokenFetching = false; let _tokenPromise = null; /** * Fetch token from Power Pages endpoint */ function fetchToken() { // Return existing promise if already fetching if (_tokenFetching && _tokenPromise) { return _tokenPromise; } _tokenFetching = true; _tokenPromise = new Promise((resolve, reject) => { // Check if jQuery is available if (typeof window.$ === 'undefined') { console.error('[Token] jQuery not available for token fetching'); _tokenFetching = false; reject(new Error('jQuery not available')); return; } window.$.ajax({ type: 'POST', url: TOKEN_ENDPOINT, data: { client_id: CLIENT_ID }, cache: false, success(data, status, jqXHR) { // Check if we got a 401 in the header const responseHeader = jqXHR.getResponseHeader('X-Responded-JSON'); const jsonResult = responseHeader ? JSON.parse(responseHeader) : null; if (jsonResult && jsonResult.status == 401) { console.log('[Token] User not authenticated, cannot fetch token'); _tokenFetching = false; reject(new Error('User not authenticated')); return; } // Token successfully fetched _token = data.access_token || data.token || data; _tokenExpiry = Date.now() + (data.expires_in ? data.expires_in * 1000 : 3600000); // Default 1 hour _tokenFetching = false; console.log('[Token] Successfully fetched PowerPages token'); resolve(_token); }, error(jqXHR, textStatus, errorThrown) { console.error( '[Token] Failed to fetch token:', textStatus, errorThrown ); _tokenFetching = false; reject(new Error(`Token fetch failed: ${textStatus}`)); }, }); }); return _tokenPromise; } /** * Get token with automatic refresh */ function getToken() { // Check if token is valid and not expired if (_token && _tokenExpiry && Date.now() < _tokenExpiry) { return Promise.resolve(_token); } // Fetch new token return fetchToken(); } // Set up window.auth object window.auth = { getToken, }; console.log('[Token] Power Pages Simple Token Manager initialized'); } /** * Get token from original token generation - no fallbacks, honest failure reporting * @returns {Promise<string>} Token or throws error */ async function getTokenFromGlobalAuth() { // Ensure the original Power Pages Simple Token Manager is set up setupPowerPagesTokenManager(); // Use only the original token generation pattern if (window.auth?.getToken && typeof window.auth.getToken === 'function') { try { const token = await window.auth.getToken(); if (token) { return token; } else { throw new Error('Token generation returned empty result'); } } catch (error) { // Honest failure reporting - no fallbacks throw new Error(`Authentication failed: ${error.message}`); } } // If window.auth.getToken is not available, fail honestly throw new Error( 'Authentication failed - no token available from original token generation' ); } /** * Load page with comprehensive error recovery and server-side template loading * @param {string} pageId - Page identifier * @param {Object} authData - Authentication data * @param {Object} options - Page options * @returns {Promise<boolean>} Success status */ async function loadPageWithErrorRecovery(pageId, authData, options) { const maxRetries = 2; const retryDelay = 1000; // 1 second // Initialize template loader for server-side assets with explicit environment const templateLoader = new TemplateLoaderService({ environment: options.environment || 'dev', branch: options.branch || 'main', useDirectMapping: options.useDirectMapping || false, }); for (let attempt = 1; attempt <= maxRetries; attempt++) { try { if (options.debug) { console.log( `[PowerPages] [LOADING] Loading page ${pageId} (attempt ${attempt}/${maxRetries})` ); } // OPTIMIZED: Load template and styles from server-side storage FIRST // This provides immediate professional UI while JS logic loads in background try { const { template, styles } = await templateLoader.loadPageAssets( pageId, options.language ); // Inject template immediately into container const container = document.getElementById(options.containerId); if (container && template) { container.innerHTML = template; console.log( `[PowerPages] 🎨 Template loaded for ${pageId} from server-side storage` ); } // Inject styles if available if (styles) { let styleElement = document.getElementById( `besper-page-styles-${pageId}` ); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = `besper-page-styles-${pageId}`; document.head.appendChild(styleElement); } styleElement.textContent = styles; console.log( `[PowerPages] 🎨 Styles applied for ${pageId} from server-side storage` ); } // Update options to include server-side template options.serverSideTemplate = true; options.templateContent = template; } catch (templateError) { console.warn( `[PowerPages] [WARN] Server-side template not available for ${pageId}, using fallback:`, templateError ); options.serverSideTemplate = false; } // Initialize page with JavaScript logic (authentication, data loading, etc.) const success = await b_esper_site(pageId, authData, options); if (success) { console.log( `[PowerPages] [SUCCESS] Page ${pageId} loaded successfully with optimized architecture` ); return true; } if (attempt < maxRetries) { if (options.debug) { console.log( `[PowerPages] ⏳ Retrying page load in ${retryDelay}ms...` ); } await new Promise(resolve => setTimeout(resolve, retryDelay)); } } catch (error) { console.error( `[PowerPages] [ERROR] Page load attempt ${attempt} failed:`, error ); if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, retryDelay)); } else { throw error; } } } return false; } /** * Initialize PowerPages-specific features after page load * Enhanced to handle modern PowerPages environment * @param {string} pageId - Page identifier * @param {Object} authData - Authentication data * @param {Object} options - Page options */ function initializePowerPagesSpecificFeatures(pageId, authData, options) { try { if (options.debug) { console.log( `[PowerPages] 🔧 Initializing PowerPages-specific features for ${pageId}` ); } // PowerPages-specific event handlers if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setupPowerPagesIntegration(pageId, authData, options); }); } else { setupPowerPagesIntegration(pageId, authData, options); } // Background token refresh if needed if (authData.authData?.tokenPending) { setupBackgroundTokenRefresh(pageId, authData, options); } } catch (error) { console.warn( '[PowerPages] Failed to initialize PowerPages-specific features:', error ); } } /** * Setup PowerPages integration features * @param {string} pageId - Page identifier * @param {Object} authData - Authentication data * @param {Object} options - Page options */ function setupPowerPagesIntegration(pageId, authData, options) { // Handle PowerPages navigation events setupPowerPagesNavigation(pageId, options.debug); // Setup PowerPages form integration setupPowerPagesFormIntegration(pageId, authData, options.debug); // Initialize PowerPages analytics if available setupPowerPagesAnalytics(pageId, options.debug); // Setup responsive behavior for PowerPages setupResponsiveBehavior(pageId, options.debug); } /** * Setup background token refresh for cases where token wasn't immediately available * @param {string} pageId - Page identifier * @param {Object} authData - Authentication data * @param {Object} options - Page options */ async function setupBackgroundTokenRefresh(pageId, authData, options) { if (options.debug) { console.log( '[PowerPages] [LOADING] Setting up background token refresh...' ); } try { // Wait a bit for auth systems to initialize await new Promise(resolve => setTimeout(resolve, 2000)); const token = await getTokenFromGlobalAuth(); if (token) { if (options.debug) { console.log( '[PowerPages] [SUCCESS] Background token acquired - updating authentication' ); } // Update authentication data authData.authData.token = token; authData.authData.isAuthenticated = true; authData.authData.tokenPending = false; // Notify page about authentication update const event = new CustomEvent('powerPagesAuthUpdated', { detail: { token, userId: authData.authData.userId }, }); window.dispatchEvent(event); } } catch (error) { console.warn('[PowerPages] Background token refresh failed:', error); } } /** * Setup PowerPages navigation integration * Enhanced for modern PowerPages environment * @param {string} pageId - Current page ID * @param {boolean} isDebugMode - Debug mode flag */ function setupPowerPagesNavigation(pageId, isDebugMode = false) { try { // Handle PowerPages-specific navigation const powerPagesLinks = document.querySelectorAll( '[data-powerpages-nav], .powerpages-nav, [href*="/"]' ); powerPagesLinks.forEach(link => { link.addEventListener('click', e => { if (isDebugMode) { console.log('[PowerPages] 🔗 Navigation event:', e.target.href); } // PowerPages navigation tracking if (window.PowerPages?.Analytics) { window.PowerPages.Analytics.trackNavigation(pageId, e.target.href); } }); }); if (isDebugMode) { console.log( `[PowerPages] [SUCCESS] Navigation setup complete (${powerPagesLinks.length} links)` ); } } catch (error) { console.warn('[PowerPages] Navigation setup failed:', error); } } /** * Setup PowerPages form integration * Enhanced with comprehensive form handling * @param {string} pageId - Page identifier * @param {Object} authData - Authentication data * @param {boolean} isDebugMode - Debug mode flag */ function setupPowerPagesFormIntegration(pageId, authData, isDebugMode = false) { try { // Handle PowerPages form submissions const forms = document.querySelectorAll( '[data-powerpages-form], .powerpages-form, form' ); forms.forEach(form => { form.addEventListener('submit', _e => { if (isDebugMode) { console.log( '[PowerPages] 📝 Form submission:', form.dataset.powerpagesForm || form.id ); } // Add authentication data to forms if user is authenticated if (authData.authData?.isAuthenticated) { const tokenInput = document.createElement('input'); tokenInput.type = 'hidden'; tokenInput.name = '_authToken'; tokenInput.value = authData.authData.token; form.appendChild(tokenInput); } // PowerPages form tracking if (window.PowerPages?.Analytics) { window.PowerPages.Analytics.trackFormSubmission(pageId, form.id); } }); }); if (isDebugMode) { console.log( `[PowerPages] [SUCCESS] Form integration setup complete (${forms.length} forms)` ); } } catch (error) { console.warn('[PowerPages] Form integration setup failed:', error); } } /** * Setup PowerPages analytics integration * @param {string} pageId - Page identifier * @param {boolean} isDebugMode - Debug mode flag */ function setupPowerPagesAnalytics(pageId, isDebugMode = false) { try { // Initialize PowerPages analytics if available if (window.PowerPages?.Analytics) { window.PowerPages.Analytics.trackPageView(pageId); if (isDebugMode) { console.log('[PowerPages] 📊 Analytics tracking initialized'); } } // Initialize Google Analytics if available if (window.gtag) { window.gtag('config', 'GA_MEASUREMENT_ID', { page_title: document.title, page_location: window.location.href, page_path: `/${pageId}`, }); if (isDebugMode) { console.log('[PowerPages] 📈 Google Analytics tracking initialized'); } } } catch (error) { console.warn('[PowerPages] Analytics setup failed:', error); } } /** * Setup responsive behavior for PowerPages environment * @param {string} pageId - Page identifier * @param {boolean} isDebugMode - Debug mode flag */ function setupResponsiveBehavior(pageId, isDebugMode = false) { try { // Ensure proper mobile behavior in PowerPages const viewport = document.querySelector('meta[name="viewport"]'); if (!viewport) { const meta = document.createElement('meta'); meta.name = 'viewport'; meta.content = 'width=device-width, initial-scale=1.0'; document.head.appendChild(meta); if (isDebugMode) { console.log( '[PowerPages] 📱 Viewport meta tag added for responsive behavior' ); } } // Handle orientation change window.addEventListener('orientationchange', () => { setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 100); }); } catch (error) { console.warn('[PowerPages] Responsive setup failed:', error); } } /** * Show user-friendly error message for PowerPages with enhanced recovery options * @param {string} pageId - Page that failed to load * @param {Error} error - Error details */ function showPowerPagesError(pageId, error) { const container = document.getElementById('besper-site-container') || document.querySelector('[id*="besper"]') || document.querySelector('.besper-container') || document.body; if (container) { const errorMessage = error?.message || 'Unknown error occurred'; const timestamp = new Date().toISOString(); container.innerHTML = ` <div style=" padding: 40px; text-align: center; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef; margin: 20px auto; max-width: 600px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; "> <div style=" color: #dc3545; font-size: 24px; margin-bottom: 16px; "> [WARN] Page Loading Error </div> <div style=" color: #495057; font-size: 16px; margin-bottom: 8px; font-weight: 500; "> Unable to load ${pageId} </div> <div style=" color: #6c757d; font-size: 14px; margin-bottom: 24px; line-height: 1.5; "> The page encountered an error during initialization. This may be due to network issues, authentication problems, or temporary service unavailability. </div> <div style="margin-bottom: 20px;"> <button onclick="location.reload()" style=" background: #007bff; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; margin-right: 12px; transition: background-color 0.2s; " onmouseover="this.style.backgroundColor='#0056b3'" onmouseout="this.style.backgroundColor='#007bff'"> [LOADING] Refresh Page </button> <button onclick="window.history.back()" style=" background: #6c757d; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color 0.2s; " onmouseover="this.style.backgroundColor='#545b62'" onmouseout="this.style.backgroundColor='#6c757d'"> ← Go Back </button> </div> <details style=" margin-top: 20px; text-align: left; padding: 12px; background: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; "> <summary style=" cursor: pointer; font-weight: 500; color: #495057; margin-bottom: 8px; ">Technical Details</summary> <div style=" font-family: 'Courier New', monospace; font-size: 12px; color: #6c757d; word-break: break-all; line-height: 1.4; "> <strong>Page:</strong> ${pageId}<br> <strong>Error:</strong> ${errorMessage}<br> <strong>Time:</strong> ${timestamp}<br> <strong>URL:</strong> ${window.location.href} </div> </details> <div style=" margin-top: 16px; font-size: 12px; color: #6c757d; "> If this problem persists, please contact support with the technical details above. </div> </div> `; } // Also log to console for debugging console.error(`[PowerPages] Error UI displayed for ${pageId}:`, error); } /** * Generate optimized PowerPages template code * This function helps developers generate the minimal PowerPages template code * Produces the auto-detection integration pattern that consolidates ALL logic into npm package * @param {string} pageId - Page identifier (optional for auto-detection) * @param {Object} options - Template options * @returns {string} PowerPages template code */ export function generatePowerPagesTemplate(pageId = null, options = {}) { const { customContainerId = 'besper-site-container', includeUserIdAssignment = false, useAutoDetection = true, } = options; const userIdSection = includeUserIdAssignment ? `{% if user %} {% assign user_id = user.userid %} {% endif %} ` : ''; // Use auto-detection pattern (recommended) or explicit page ID const integrationCall = useAutoDetection || !pageId ? `window.besperAutoPageIntegration({ user: {% if user %}{ id: "{{ user.contactid }}", contactid: "{{ user.contactid }}" }{% else %}null{% endif %}, language: "{{ website.selected_language.code }}" });` : `window.besperPageIntegration('${pageId}', { user: {% if user %}{ id: "{{ user.contactid }}", contactid: "{{ user.contactid }}" }{% else %}null{% endif %}, language: "{{ website.selected_language.code }}", environment: "{{ settings.environment }}", branch: "{{ settings.bsp_branch }}" });`; return `${userIdSection}<!-- B-esper Site Container --> <div id="${customContainerId}" style="width: 100%; min-height: 600px;"></div> <!-- B-esper Site Integration with Auto-Detection --> <script src="https://unpkg.com/besper-frontend-site-{{ settings.environment }}-{{ settings.bsp_branch }}@latest/dist/bespersite.js"></script> <script> // Optimized PowerPages integration with automatic URL detection ${integrationCall} </script>`; } // Export global function for PowerPages templates if (typeof window !== 'undefined') { window.besperPageIntegration = initializePowerPagesIntegration; window.besperAutoPageIntegration = autoDetectPageIntegration; window.generateBesperTemplate = generatePowerPagesTemplate; } export default { initializePowerPagesIntegration, generatePowerPagesTemplate, };