besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
565 lines (501 loc) • 17.3 kB
JavaScript
/**
* User Management New Page Script - Bulletproof loading pattern
* Implements modern user management UI with APIM operators
*/
class UserManagementNewPage {
constructor(options = {}) {
this.options = {
containerId: 'besper-site-content',
environment: 'prod',
...options,
};
this.initialized = false;
this.users = [];
this.loading = false;
this.workspaceId = null;
// BULLETPROOF: Defer all complex operations to avoid blocking class export
this.authService = null;
this.operatorsService = null;
this.isAuthenticated = false;
// Initialize auth safely in next tick to ensure class export completes first
if (typeof window !== 'undefined') {
setTimeout(() => {
this.initializeAuth();
}, 1);
}
}
/**
* Initialize authentication safely without blocking class export
*/
initializeAuth() {
try {
this.authService = this.getAuthService();
this.operatorsService = this.getOperatorsService();
} catch (error) {
console.warn('Auth initialization failed, using fallback:', error);
this.authService = this.createFallbackAuthService();
this.operatorsService = this.createFallbackOperatorsService();
}
}
/**
* 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;
}
return this.createFallbackAuthService();
}
/**
* Get operators service from global scope or create simple fallback
*/
getOperatorsService() {
// Try to access global page operators service if available
if (typeof window !== 'undefined' && window.pageOperatorsService) {
return window.pageOperatorsService;
}
return this.createFallbackOperatorsService();
}
/**
* Create safe fallback auth service
*/
createFallbackAuthService() {
return {
isUserAuthenticated: () => {
try {
return (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function' &&
!!window.auth.getToken()
);
} catch (error) {
return false;
}
},
getToken: () => {
try {
if (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function'
) {
return window.auth.getToken();
}
} catch (error) {
console.warn('Token access failed:', error);
}
return null;
},
getUserPermission: key => {
try {
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;
} catch (error) {
console.warn('User permission access failed:', error);
return null;
}
},
};
}
/**
* Create safe fallback operators service
*/
createFallbackOperatorsService() {
return {
getUsers: async () => {
console.warn(
'Operators service not available, returning empty user list'
);
return [];
},
inviteUser: async () => {
console.warn('Operators service not available, invite user failed');
return { success: false, message: 'Service not available' };
},
removeUser: async () => {
console.warn('Operators service not available, remove user failed');
return { success: false, message: 'Service not available' };
},
};
}
/**
* Initialize the page - IMMEDIATE RENDERING
* Shows UI instantly without waiting for authentication
*/
async initialize(_data = {}) {
if (this.initialized) return;
try {
// IMMEDIATE: Show the UI without any loading delays
this.renderImmediateUI();
// IMMEDIATE: Setup basic interactions
this.setupInteractions();
this.initialized = true;
// DEFERRED: Initialize authentication features in background
this.initializeAuthenticationFeaturesInBackground();
} catch (error) {
console.error('Error initializing user management page:', error);
this.showError(error);
}
}
/**
* Render immediate UI structure - shows content instantly
*/
renderImmediateUI() {
const container = document.getElementById(this.options.containerId);
if (!container) return;
// Clear any existing loading indicators
container.innerHTML = '';
// Show immediate user 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;">User Management</h1>
<p style="color: #6c757d; font-size: 14px; margin: 0;">Manage user access and permissions</p>
</div>
<!-- Action Buttons -->
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 2rem;">
<button id="inviteUserBtn" style="padding: 10px 20px; border: none; border-radius: 6px; background: #022d54; color: white; cursor: pointer; font-size: 14px;">
➕ Invite User
</button>
</div>
<!-- Users List with Skeleton Loading -->
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;">
<div style="padding: 1rem; border-bottom: 1px solid #e0e0e0;">
<h3 style="margin: 0; color: #022d54; font-size: 16px; font-weight: 500;">Users</h3>
</div>
<div id="page-content-area" style="padding: 2rem;">
<!-- Skeleton Loading Animation -->
<div style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; animation: pulse 1.5s ease-in-out infinite;"></div>
<div style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 80%; animation: pulse 1.5s ease-in-out infinite;"></div>
<div style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 60%; animation: pulse 1.5s ease-in-out infinite;"></div>
<div style="height: 100px; background: #e0e0e0; border-radius: 4px; animation: pulse 1.5s ease-in-out infinite;"></div>
</div>
</div>
</div>
</div>
<!-- Skeleton Loading Animation -->
<style>
pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
`;
}
/**
* Setup interactions - immediate setup without waiting
*/
setupInteractions() {
// Setup invite user button
const inviteBtn = document.getElementById('inviteUserBtn');
if (inviteBtn) {
inviteBtn.addEventListener('click', () => {
this.showInviteUserModal();
});
}
// Setup row click handlers (will be rebound when real content loads)
document.addEventListener('click', e => {
if (e.target.closest('.user-row')) {
this.selectUser(e.target.closest('.user-row').dataset.userId);
}
});
}
/**
* Initialize authentication features completely in background - NO UI BLOCKING
*/
async initializeAuthenticationFeaturesInBackground() {
// Use requestIdleCallback for true background processing
const initializeAuth = () => {
try {
// Ensure services are ready
if (!this.authService) {
this.initializeAuth();
}
// Check authentication status without blocking
if (this.authService) {
this.isAuthenticated = this.authService.isUserAuthenticated();
if (this.isAuthenticated) {
this.workspaceId =
this.authService.getUserPermission('workspaceId');
// Load data in background
this.loadDataInBackground();
} 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 data in background
*/
async loadDataInBackground() {
try {
console.log('Loading users in background...');
await this.loadUsers();
} catch (error) {
console.error('Background data loading failed:', error);
}
}
async loadUsers() {
try {
this.setLoadingState(true);
if (!this.operatorsService) {
console.warn('Operators service not available');
return;
}
const response = await this.operatorsService.callOperator(
'user_management',
'list_users',
{
authData: this.authService.getAuthData
? this.authService.getAuthData()
: { token: this.authService.getToken() },
workspaceId: this.workspaceId,
}
);
if (response.success) {
this.users = response.data.users || [];
this.renderUsersTable();
}
} catch (error) {
console.error('Error loading users:', error);
this.showError('Failed to load users');
} finally {
this.setLoadingState(false);
}
}
async inviteUser(userData) {
try {
if (!this.operatorsService) {
console.warn('Operators service not available');
return;
}
const response = await this.operatorsService.callOperator(
'user_management',
'invite_user',
{
authData: this.authService.getAuthData
? this.authService.getAuthData()
: { token: this.authService.getToken() },
workspaceId: this.workspaceId,
...userData,
}
);
if (response.success) {
await this.loadUsers();
this.hideInviteUserModal();
this.showSuccess('User invited successfully');
}
} catch (error) {
console.error('Error inviting user:', error);
this.showError('Failed to invite user');
}
}
renderUsersTable() {
const tableBody = document.getElementById('usersTableBody');
if (!tableBody) return;
tableBody.innerHTML = this.users
.map(
user => `
<tr class="user-row" data-user-id="${user.id}">
<td>
<div class="user-info">
<div class="user-name">${user.name}</div>
<div class="user-email">${user.email}</div>
</div>
</td>
<td>
<span class="role-badge ${user.role}">${user.role}</span>
</td>
<td>
<span class="status-badge ${user.status}">${user.status}</span>
</td>
<td>${user.lastActive || 'Never'}</td>
</tr>
`
)
.join('');
}
showInviteUserModal() {
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = `
<div class="modal">
<div class="modal-header">
<h2>Invite User</h2>
<button class="close-modal">×</button>
</div>
<div class="modal-body">
<form id="inviteUserForm">
<div class="form-group">
<label>Email *</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Role</label>
<select name="role">
<option value="member">Member</option>
<option value="admin">Admin</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-outline close-modal">Cancel</button>
<button class="btn btn-primary" onclick="this.submitInviteUser()">Send Invite</button>
</div>
</div>
`;
document.body.appendChild(modal);
modal.querySelectorAll('.close-modal').forEach(btn => {
btn.addEventListener('click', () => this.hideInviteUserModal());
});
}
hideInviteUserModal() {
document.querySelector('.modal-overlay')?.remove();
}
async submitInviteUser() {
const form = document.getElementById('inviteUserForm');
if (!form) return;
const formData = new FormData(form);
await this.inviteUser({
email: formData.get('email'),
role: formData.get('role'),
});
}
setLoadingState(loading) {
this.loading = loading;
const loadingIndicator = document.getElementById('loadingIndicator');
if (loadingIndicator) {
loadingIndicator.style.display = loading ? 'block' : 'none';
}
}
/**
* Show error message
*/
showError(message) {
console.error(message);
const contentArea = document.getElementById('page-content-area');
if (contentArea) {
contentArea.innerHTML = `
<div style="text-align: center; padding: 2rem; color: #d32f2f;">
<h3>Error</h3>
<p>${message}</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>
`;
}
}
showSuccess(message) {
console.log(message);
// TODO: Implement success notification
}
showUnauthenticatedView() {
const contentArea = document.getElementById('page-content-area');
if (contentArea) {
contentArea.innerHTML = `
<div style="text-align: center; padding: 2rem;">
<h2 style="color: #022d54;">Authentication Required</h2>
<p style="color: #6c757d;">Please log in to access user management.</p>
</div>
`;
}
}
selectUser(userId) {
console.log('User selected:', userId);
// TODO: Implement user selection logic
}
getPageContent() {
return `
<div style="padding: 1rem;">
<div style="margin-bottom: 2rem;">
<h2 style="color: #022d54; margin-bottom: 0.5rem;">Team Members</h2>
<div id="loadingIndicator" style="display: none; color: #6c757d;">Loading users...</div>
</div>
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f8f9fa; border-bottom: 1px solid #e0e0e0;">
<th style="padding: 1rem; text-align: left; color: #022d54; font-weight: 500;">User</th>
<th style="padding: 1rem; text-align: left; color: #022d54; font-weight: 500;">Role</th>
<th style="padding: 1rem; text-align: left; color: #022d54; font-weight: 500;">Status</th>
<th style="padding: 1rem; text-align: left; color: #022d54; font-weight: 500;">Last Active</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
</table>
</div>
</div>
`;
}
getPageTitle() {
return 'User Management';
}
getPageSubtitle() {
return 'Manage team members and permissions';
}
destroy() {
this.hideInviteUserModal();
}
}
// BULLETPROOF EXPORT MECHANISM - Ensures class is always available
(function () {
'use strict';
// Multiple export strategies to ensure maximum compatibility
// Strategy 1: Direct window assignment (most reliable)
if (typeof window !== 'undefined') {
window.UserManagementNewPage = UserManagementNewPage;
}
// Strategy 2: Module exports for Node.js environments
if (typeof module !== 'undefined' && module.exports) {
module.exports = UserManagementNewPage;
}
// Strategy 3: Global fallback
if (typeof global !== 'undefined') {
global.UserManagementNewPage = UserManagementNewPage;
}
// Strategy 4: AMD/RequireJS support
if (typeof define === 'function' && define.amd) {
define([], function () {
return UserManagementNewPage;
});
}
// Strategy 5: Self-executing verification
setTimeout(function () {
if (typeof window !== 'undefined' && !window.UserManagementNewPage) {
console.warn('UserManagementNewPage export failed, retrying...');
window.UserManagementNewPage = UserManagementNewPage;
}
}, 10);
})();