besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
422 lines (374 loc) • 12.4 kB
JavaScript
/**
* Credentials Display Component
* Handles the display and management of bot credentials
*/
export class CredentialsDisplay {
constructor(widget, state, i18n = null) {
this.widget = widget;
this.state = state;
this.i18n = i18n;
this.isSecretVisible = false;
}
/**
* Update language
* @param {string} language - New language code
*/
updateLanguage(language) {
if (this.i18n) {
console.log(`[CredentialsDisplay] Language updated to: ${language}`);
}
}
/**
* Get translated text
* @param {string} key - Translation key
* @param {Object} variables - Variables for substitution
* @returns {string} Translated text
*/
t(key, variables = {}) {
return this.i18n ? this.i18n.t(key, variables) : key;
}
/**
* Generate the HTML for credentials display
* @param {Object} credentials - Credentials object
* @returns {string} Credentials HTML string
*/
getHTML(credentials) {
return `
<div class="bm-credentials-container">
<div class="bm-form-group">
<label class="bm-form-label">${this.t('configuration.credentials.botId')}</label>
<div class="bm-credential-field">
<input type="text" class="bm-form-input" id="bm-botIdInput" readonly value="${credentials.botId || ''}">
<button class="bm-copy-btn" data-copy-target="bm-botIdInput" title="${this.t('tooltips.copyBotId')}">
${this.getCopyIcon()}
</button>
</div>
</div>
<div class="bm-form-group">
<label class="bm-form-label">${this.t('configuration.credentials.managementId')}</label>
<div class="bm-credential-field">
<input type="text" class="bm-form-input" id="bm-managementIdInput" readonly value="${credentials.managementId || ''}">
<button class="bm-copy-btn" data-copy-target="bm-managementIdInput" title="${this.t('tooltips.copyManagementId')}">
${this.getCopyIcon()}
</button>
</div>
</div>
<div class="bm-form-group">
<label class="bm-form-label">${this.t('configuration.credentials.managementSecret')}</label>
<div class="bm-credential-field">
<input type="password" class="bm-form-input" id="bm-managementSecretInput" readonly value="${credentials.managementSecret || ''}">
<button class="bm-toggle-btn" id="bm-toggleSecretBtn" title="${this.t('tooltips.toggleVisibility')}">
${this.getEyeIcon()}
</button>
<button class="bm-copy-btn" data-copy-target="bm-managementSecretInput" title="${this.t('tooltips.copyManagementSecret')}">
${this.getCopyIcon()}
</button>
</div>
</div>
</div>
`;
}
/**
* Get copy icon SVG
*/
getCopyIcon() {
return `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
`;
}
/**
* Get eye icon SVG
*/
getEyeIcon() {
return `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
`;
}
/**
* Get eye off icon SVG
*/
getEyeOffIcon() {
return `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
</svg>
`;
}
/**
* Setup event listeners for credentials display
* @param {Object} callbacks - Callback functions for events
*/
setupEventListeners(callbacks = {}) {
// Copy buttons
const copyButtons = this.widget.querySelectorAll(
'.bm-copy-btn[data-copy-target]'
);
copyButtons.forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.getAttribute('data-copy-target');
this.copyToClipboard(targetId, callbacks.onCopy);
});
});
// Toggle secret visibility
const toggleSecretBtn = this.widget.querySelector('#bm-toggleSecretBtn');
if (toggleSecretBtn) {
toggleSecretBtn.addEventListener('click', () => {
this.toggleSecretVisibility(callbacks.onToggleVisibility);
});
}
}
/**
* Copy text to clipboard
* @param {string} targetId - ID of the input element to copy from
* @param {Function} callback - Optional callback function
*/
copyToClipboard(targetId, callback) {
const input = this.widget.querySelector(`#${targetId}`);
if (!input) return;
// Select the text
input.select();
input.setSelectionRange(0, 99999); // For mobile devices
try {
// Try modern clipboard API first
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(input.value)
.then(() => {
this.showCopyFeedback(input);
if (callback) callback(targetId, input.value);
})
.catch(() => {
// Fallback to execCommand
this.fallbackCopy(input, targetId, callback);
});
} else {
// Fallback to execCommand
this.fallbackCopy(input, targetId, callback);
}
} catch (err) {
console.error('Copy failed:', err);
}
}
/**
* Fallback copy method using execCommand
* @param {HTMLElement} input - Input element
* @param {string} targetId - Target ID
* @param {Function} callback - Optional callback function
*/
fallbackCopy(input, targetId, callback) {
try {
const successful = document.execCommand('copy');
if (successful) {
this.showCopyFeedback(input);
if (callback) callback(targetId, input.value);
}
} catch (err) {
console.error('Fallback copy failed:', err);
}
}
/**
* Show copy feedback
* @param {HTMLElement} input - Input element that was copied
*/
showCopyFeedback(input) {
const originalBorder = input.style.border;
const originalBackground = input.style.background;
// Visual feedback
input.style.border = '2px solid #10b981';
input.style.background = '#f0fdf4';
// Find associated copy button and show feedback
const credentialField = input.closest('.bm-credential-field');
const copyBtn = credentialField?.querySelector('.bm-copy-btn');
if (copyBtn) {
const originalContent = copyBtn.innerHTML;
copyBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
`;
copyBtn.style.background = '#10b981';
copyBtn.style.color = 'white';
setTimeout(() => {
copyBtn.innerHTML = originalContent;
copyBtn.style.background = '';
copyBtn.style.color = '';
}, 2000);
}
setTimeout(() => {
input.style.border = originalBorder;
input.style.background = originalBackground;
}, 2000);
}
/**
* Toggle secret visibility
* @param {Function} callback - Optional callback function
*/
toggleSecretVisibility(callback) {
const secretInput = this.widget.querySelector('#bm-managementSecretInput');
const toggleBtn = this.widget.querySelector('#bm-toggleSecretBtn');
if (!secretInput || !toggleBtn) return;
this.isSecretVisible = !this.isSecretVisible;
// Toggle input type
secretInput.type = this.isSecretVisible ? 'text' : 'password';
// Update button icon
toggleBtn.innerHTML = this.isSecretVisible
? this.getEyeOffIcon()
: this.getEyeIcon();
toggleBtn.title = this.isSecretVisible ? 'Hide secret' : 'Show secret';
if (callback) {
callback(this.isSecretVisible);
}
}
/**
* Update credentials data
* @param {Object} credentials - New credentials
*/
updateCredentials(credentials) {
const botIdInput = this.widget.querySelector('#bm-botIdInput');
const managementIdInput = this.widget.querySelector(
'#bm-managementIdInput'
);
const managementSecretInput = this.widget.querySelector(
'#bm-managementSecretInput'
);
if (botIdInput) botIdInput.value = credentials.botId || '';
if (managementIdInput)
managementIdInput.value = credentials.managementId || '';
if (managementSecretInput)
managementSecretInput.value = credentials.managementSecret || '';
}
/**
* Get current credentials data
* @returns {Object} Current credentials
*/
getCredentials() {
const botIdInput = this.widget.querySelector('#bm-botIdInput');
const managementIdInput = this.widget.querySelector(
'#bm-managementIdInput'
);
const managementSecretInput = this.widget.querySelector(
'#bm-managementSecretInput'
);
return {
botId: botIdInput?.value || '',
managementId: managementIdInput?.value || '',
managementSecret: managementSecretInput?.value || '',
};
}
/**
* Validate credentials
* @returns {Object} Validation result
*/
validateCredentials() {
const credentials = this.getCredentials();
const errors = [];
if (!credentials.botId) {
errors.push('Bot ID is required');
}
if (!credentials.managementId) {
errors.push('Management ID is required');
}
if (!credentials.managementSecret) {
errors.push('Management Secret is required');
}
// Validate format (basic validation)
if (credentials.botId && !/^[a-zA-Z0-9-_]+$/.test(credentials.botId)) {
errors.push('Bot ID contains invalid characters');
}
if (
credentials.managementId &&
!/^[a-zA-Z0-9-_]+$/.test(credentials.managementId)
) {
errors.push('Management ID contains invalid characters');
}
return {
isValid: errors.length === 0,
errors,
credentials,
};
}
/**
* Show validation errors
* @param {Array} errors - Array of error messages
*/
showValidationErrors(errors) {
// Remove existing error messages
this.clearValidationErrors();
errors.forEach((error, index) => {
const errorElement = document.createElement('div');
errorElement.className = 'bm-credential-error';
errorElement.textContent = error;
errorElement.id = `bm-credential-error-${index}`;
const container = this.widget.querySelector('.bm-credentials-container');
if (container) {
container.appendChild(errorElement);
}
});
}
/**
* Clear validation errors
*/
clearValidationErrors() {
const errors = this.widget.querySelectorAll('.bm-credential-error');
errors.forEach(error => error.remove());
}
/**
* Mask credentials for display
* @param {Object} credentials - Credentials to mask
* @returns {Object} Masked credentials
*/
maskCredentials(credentials) {
return {
botId: credentials.botId,
managementId: credentials.managementId,
managementSecret: credentials.managementSecret
? credentials.managementSecret.replace(/./g, '•')
: '',
};
}
/**
* Export credentials (for backup/migration)
* @returns {string} JSON string of credentials
*/
exportCredentials() {
const credentials = this.getCredentials();
return JSON.stringify(credentials, null, 2);
}
/**
* Import credentials from JSON
* @param {string} jsonString - JSON string of credentials
* @returns {boolean} Success status
*/
importCredentials(jsonString) {
try {
const credentials = JSON.parse(jsonString);
this.updateCredentials(credentials);
return true;
} catch (error) {
console.error('Failed to import credentials:', error);
return false;
}
}
/**
* Reset secret visibility to default (hidden)
*/
resetSecretVisibility() {
if (this.isSecretVisible) {
this.toggleSecretVisibility();
}
}
/**
* Cleanup method for component destruction
*/
destroy() {
this.clearValidationErrors();
this.resetSecretVisibility();
}
}