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
JavaScript
/**
* 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,
};