besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
549 lines (486 loc) • 19.6 kB
JavaScript
/**
* Manage Workspace Page JavaScript - Pure JS with server-side template loading
* Manages organizational workspaces and their hierarchies
* Uses TemplateLoaderService for HTML/CSS from server-side storage
*/
class ManageWorkspacePage {
constructor(options = {}) {
this.options = {
containerId: 'besper-site-content',
environment: 'dev',
...options,
};
this.initialized = false;
this.authService = this.getAuthService();
this.isAuthenticated = false;
// Initialize template loader for server-side assets
this.templateLoader = new window.TemplateLoaderService({
environment: this.options.environment,
branch: this.options.branch || 'main',
});
}
/**
* Get authentication service from global scope or create simple fallback
*/
getAuthService() {
// Try to access global token auth service if available
if (typeof window !== 'undefined' && window.tokenAuthService) {
return window.tokenAuthService;
}
// Fallback implementation using global window.auth
return {
isUserAuthenticated: () => {
return (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function' &&
!!window.auth.getToken()
);
},
getToken: () => {
if (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function'
) {
return window.auth.getToken();
}
return null;
},
getUserPermission: key => {
if (typeof window === 'undefined') return null;
const mappings = {
contactId: window.contact_id || window.user_contactid,
userName: window.user_name,
name: window.user_name,
userEmail: window.user_email,
email: window.user_email,
workspaceId: window.workspace_id,
accountId: window.account_id,
subscriptionId: window.subscription_id,
};
return mappings[key] || null;
},
};
}
/**
* Get root API endpoint
*/
getRootApiEndpoint() {
const baseEndpoint = this.getBaseAPIEndpoint();
return `${baseEndpoint}/api`;
}
/**
* Get base API endpoint from environment
*/
getBaseAPIEndpoint() {
// Use the build-time configured endpoint
if (
typeof process !== 'undefined' &&
process.env &&
process.env.API_ENDPOINT
) {
return process.env.API_ENDPOINT;
}
// Fallback for runtime detection
if (typeof window !== 'undefined') {
// Check if endpoint is provided in window object
if (window.bSiteApiEndpoint) {
return window.bSiteApiEndpoint;
}
// Development fallback
if (window.location.hostname === 'localhost') {
return process.env.API_ENDPOINT;
}
}
// Default production endpoint
return process.env.API_ENDPOINT;
}
/**
* Initialize the workspace management page with server-side template architecture
* @param {Object} data - Data object (legacy parameter, not used)
* @param {Object} options - Options including serverSideTemplate flag
*/
async initialize(_data = {}, _options = {}) {
if (this.initialized) return;
try {
// Load template and styles from server-side storage
console.log('[LOADING] Loading page assets from server-side storage...');
const { template, styles } =
await this.templateLoader.loadPageAssets('manage-workspace');
// Render the server-side template immediately
const container = document.getElementById(this.options.containerId);
if (container) {
container.innerHTML = template;
console.log(
'[SUCCESS] Server-side template rendered for professional UI'
);
}
// Apply styles if available
if (styles) {
let styleElement = document.getElementById(
'besper-manage-workspace-styles'
);
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'besper-manage-workspace-styles';
document.head.appendChild(styleElement);
}
styleElement.textContent = styles;
console.log('[SUCCESS] Server-side styles applied for professional UI');
}
// Setup JavaScript interactions on the rendered template
this.setupInteractionsOnServerTemplate();
this.initialized = true;
// DEFERRED: Initialize authentication features in background
// This runs completely in background without blocking UI
this.initializeAuthenticationFeaturesInBackground();
} catch (error) {
console.error('Error initializing workspace management page:', error);
console.log('[WARN] Fallback to client-side rendering');
// Fallback: Show the workspace management UI using client-side fallback
this.renderFallbackUI();
this.setupInteractions();
}
}
/**
* Render fallback UI when server-side template fails
*/
renderFallbackUI() {
const container = document.getElementById(this.options.containerId);
if (!container) return;
// Clear any existing loading indicators
container.innerHTML = '';
// Show immediate workspace management interface
container.innerHTML = `
<div style="background: #f8f9fa; color: #022d54; font-family: Arial, sans-serif;">
<div style="max-width: 1200px; margin: 0 auto; padding: 2rem 1rem;">
<!-- Header -->
<div style="margin-bottom: 2rem;">
<h1 style="color: #022d54; font-weight: 300; font-size: 28px; margin-bottom: 0.5rem;">Workspace Management</h1>
<p style="color: #6c757d; font-size: 14px; margin: 0;">Manage organizational workspaces and their hierarchies</p>
</div>
<!-- Action Buttons -->
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 2rem;">
<button style="padding: 10px 20px; border: 1px solid #022d54; border-radius: 6px; background: white; color: #022d54; cursor: pointer; font-size: 14px;">
📤 Export
</button>
<button style="padding: 10px 20px; border: none; border-radius: 6px; background: #022d54; color: white; cursor: pointer; font-size: 14px;">
➕ Create Workspace
</button>
</div>
<!-- Stats Cards with Skeleton Loading -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;">
<div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Workspaces</div>
<div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;">
<div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 40px;"></div>
</div>
<div style="color: #6c757d; font-size: 14px;">Active workspaces</div>
</div>
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;">
<div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Users</div>
<div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;">
<div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 60px;"></div>
</div>
<div style="color: #6c757d; font-size: 14px;">Across all workspaces</div>
</div>
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;">
<div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Licenses</div>
<div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;">
<div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 50px;"></div>
</div>
<div style="color: #6c757d; font-size: 14px;">License allocation</div>
</div>
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;">
<div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Cost Pools</div>
<div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;">
<div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 30px;"></div>
</div>
<div style="color: #6c757d; font-size: 14px;">Unique cost centers</div>
</div>
</div>
<!-- Workspaces Table -->
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;">
<div style="padding: 1rem; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center;">
<h3 style="margin: 0; color: #022d54; font-size: 16px; font-weight: 500;">All Workspaces</h3>
<input type="text" placeholder="Search workspaces..." style="padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px; width: 200px;">
</div>
<div id="workspace-table-content" style="padding: 2rem;">
<!-- Skeleton Loading Animation -->
<div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem;"></div>
<div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 80%;"></div>
<div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 60%;"></div>
<div class="skeleton-loader" style="height: 100px; background: #e0e0e0; border-radius: 4px;"></div>
</div>
</div>
</div>
</div>
<!-- Skeleton Loading Animation -->
<style>
.skeleton-loader {
animation: pulse 1.5s ease-in-out infinite;
}
pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
`;
}
/**
* Setup interactions immediately
*/
setupInteractions() {
// Basic interaction setup without waiting for authentication
console.log('Workspace Management interactions ready');
}
/**
* Setup interactions on server-side rendered template
* This method is called when the template was loaded from server-side storage
*/
setupInteractionsOnServerTemplate() {
console.log('Setting up interactions on server-side template...');
// The HTML is already rendered from server-side storage
// Just add event listeners and JavaScript behavior
// Add click handlers for action buttons
this.setupActionButtonHandlers();
// Setup table interactions
this.setupTableInteractions();
// Setup responsive behavior
this.setupResponsiveBehavior();
console.log('[SUCCESS] Server-side template interactions ready');
}
/**
* Setup action button handlers
*/
setupActionButtonHandlers() {
// Add event listeners for buttons in the header actions
const headerActions = document.querySelector(
'.workspace-header .header-actions'
);
if (headerActions) {
headerActions.addEventListener('click', e => {
const button = e.target.closest('button');
if (button) {
console.log('Action button clicked:', button.textContent);
// Handle button actions here
}
});
}
}
/**
* Setup table interactions
*/
setupTableInteractions() {
// Add event listeners for table actions
const tableContainer = document.querySelector('.table-container');
if (tableContainer) {
tableContainer.addEventListener('click', e => {
const actionBtn = e.target.closest('.skeleton-btn-sm, .action-btn');
if (actionBtn) {
console.log('Table action clicked');
// Handle table actions here
}
});
}
}
/**
* Setup responsive behavior
*/
setupResponsiveBehavior() {
// Add responsive event listeners if needed
console.log('Responsive behavior setup complete');
}
/**
* Initialize authentication features completely in background - NO UI BLOCKING
*/
async initializeAuthenticationFeaturesInBackground() {
// Use requestIdleCallback for true background processing
const initializeAuth = () => {
try {
// Check authentication status without blocking
this.isAuthenticated = this.authService.isUserAuthenticated();
if (this.isAuthenticated) {
// Load workspace data in background
this.loadWorkspaceDataInBackground();
} else {
this.showUnauthenticatedView();
}
} catch (error) {
console.warn(
'Background authentication features initialization failed:',
error
);
// Continue silently - authentication is not critical for UI display
}
};
// Use requestIdleCallback for non-blocking background execution
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(initializeAuth, { timeout: 5000 });
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(initializeAuth, 50);
}
}
/**
* Load workspace data in background
*/
async loadWorkspaceDataInBackground() {
try {
console.log('Loading workspace management data in background...');
// TODO: Replace with actual workspace data loading
// Example:
// const workspaceData = await this.fetchWorkspaceData();
// this.updateStatsWithData(workspaceData);
// this.updateTableWithData(workspaceData);
// For now, simulate data loading and update UI
setTimeout(() => {
this.updateUIWithWorkspaceData();
}, 1000);
} catch (error) {
console.error('Background workspace data loading failed:', error);
this.showDataLoadingError();
}
}
/**
* Update UI with actual workspace data (replace skeleton loading)
*/
updateUIWithWorkspaceData() {
console.log('[Workspace] 📊 Updating skeleton elements with real data');
// Update stats with real data
this.updateStatsData();
// Update table with real data
this.updateTableData();
// Transition skeleton elements to loaded state
this.transitionSkeletonToLoaded();
}
/**
* Update statistics cards with real data
*/
updateStatsData() {
const statValues = document.querySelectorAll('.stat-value .skeleton-text');
const statData = ['12', '1,247', '486', '8'];
statValues.forEach((element, index) => {
if (index < statData.length) {
element.textContent = statData[index];
}
});
}
/**
* Update table with real workspace data
*/
updateTableData() {
const tableBody = document.getElementById('workspaces-tbody');
if (!tableBody) return;
// Replace skeleton rows with real data
tableBody.innerHTML = `
<tr>
<td>Global Organization</td>
<td>—</td>
<td>1,247</td>
<td>Active</td>
<td>2023-01-15</td>
<td>
<button class="bsp-btn bsp-btn-sm" title="View">👁️</button>
<button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button>
<button class="bsp-btn bsp-btn-sm" title="Users">👥</button>
</td>
</tr>
<tr>
<td style="padding-left: 2rem;">└ Enterprise Division</td>
<td>Global Organization</td>
<td>342</td>
<td>Active</td>
<td>2023-02-20</td>
<td>
<button class="bsp-btn bsp-btn-sm" title="View">👁️</button>
<button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button>
<button class="bsp-btn bsp-btn-sm" title="Users">👥</button>
</td>
</tr>
<tr>
<td style="padding-left: 2rem;">└ Product Development</td>
<td>Global Organization</td>
<td>186</td>
<td>Active</td>
<td>2023-03-10</td>
<td>
<button class="bsp-btn bsp-btn-sm" title="View">👁️</button>
<button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button>
<button class="bsp-btn bsp-btn-sm" title="Users">👥</button>
</td>
</tr>
`;
}
/**
* Transition skeleton elements to loaded state with smooth animation
*/
transitionSkeletonToLoaded() {
setTimeout(() => {
const skeletonElements = document.querySelectorAll('.skeleton-loading');
skeletonElements.forEach((element, index) => {
// Stagger the transition for a nice effect
setTimeout(() => {
element.classList.add('loaded');
// Remove skeleton-loading class after transition completes
setTimeout(() => {
element.classList.remove('skeleton-loading');
}, 300);
}, index * 30); // 30ms stagger between elements
});
console.log(
'[Workspace] [SUCCESS] Skeleton to loaded transition completed'
);
}, 500); // Wait a bit for data to settle
}
/**
* Show data loading error
*/
showDataLoadingError() {
const tableContent = document.getElementById('workspace-table-content');
if (tableContent) {
tableContent.innerHTML = `
<div style="text-align: center; padding: 2rem;">
<h2 style="color: #d32f2f; margin-bottom: 1rem;">Error Loading Data</h2>
<p style="color: #6c757d; margin-bottom: 1.5rem;">Unable to load workspace data. Please try again.</p>
<button onclick="location.reload()" style="padding: 10px 20px; background: #022d54; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;">
Retry
</button>
</div>
`;
}
}
/**
* Show error message
*/
showError(error) {
const container = document.getElementById(this.options.containerId);
if (container) {
container.innerHTML = `
<div style="background: #f8f9fa; display: flex; align-items: center; justify-content: center;">
<div style="text-align: center; padding: 2rem; background: white; border-radius: 8px; border: 1px solid #e0e0e0; max-width: 500px;">
<h3 style="color: #d32f2f; margin-bottom: 1rem;">Error Loading Workspace Management</h3>
<p style="color: #6c757d; margin-bottom: 1.5rem;">${error.message || error}</p>
<button onclick="location.reload()" style="padding: 10px 20px; background: #022d54; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;">
Try Again
</button>
</div>
</div>
`;
}
}
/**
* Cleanup resources
*/
destroy() {
this.initialized = false;
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = ManageWorkspacePage;
} else if (typeof window !== 'undefined') {
window.ManageWorkspacePage = ManageWorkspacePage;
}