besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
749 lines (659 loc) • 21.3 kB
JavaScript
/**
* Contact Us Page JavaScript - Optimized for fast rendering
* Handles contact form submission and customer outreach integration
*/
class ContactUsPage {
constructor(options = {}) {
this.options = {
containerId: 'besper-site-content',
environment: 'prod',
...options,
};
this.initialized = false;
this.form = null;
this.submitButton = null;
this.messagesContainer = null;
this.authService = this.getAuthService();
this.isAuthenticated = false;
}
/**
* 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 contact us page - IMMEDIATE RENDERING with skeleton loading
* Shows UI instantly without waiting for authentication or token generation
*/
async initialize(data = {}) {
if (this.initialized) return;
try {
// IMMEDIATE: Show the form UI without any loading delays
this.renderImmediateUI();
// IMMEDIATE: Setup form elements and interactions
this.setupFormElements();
this.initializeFormHandling();
// IMMEDIATE: Handle URL parameters
this.handleUrlParameters();
// IMMEDIATE: Pre-fill form if data provided
if (data.formData) {
this.prefillForm(data.formData);
}
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 contact us page:', error);
this.showError(error);
}
}
/**
* Render immediate UI without any blocking operations
*/
renderImmediateUI() {
// Ensure form is visible and interactive immediately
const form = document.getElementById('contact-form');
if (form) {
form.style.opacity = '1';
form.style.pointerEvents = 'auto';
}
// Remove any loading states or overlays
const loadingIndicators = document.querySelectorAll(
'.bsp-loading-indicator, .bsp-skeleton-loader'
);
loadingIndicators.forEach(indicator => indicator.remove());
}
/**
* Setup form elements immediately
*/
setupFormElements() {
this.form = document.getElementById('contact-form');
this.submitButton = this.form?.querySelector('button[type="submit"]');
this.messagesContainer = document.getElementById('form-messages');
if (!this.form) {
throw new Error('Contact form not found');
}
}
/**
* 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) {
// Pre-fill user information from token
this.prefillUserInformation();
this.showAuthenticatedOptions();
}
} catch (error) {
console.warn(
'Background authentication features initialization failed:',
error
);
// Continue silently - authentication is not critical for contact form
}
};
// Use requestIdleCallback for non-blocking background execution
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(initializeAuth, { timeout: 5000 });
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(initializeAuth, 50);
}
}
/**
* Pre-fill user information from authentication token
*/
prefillUserInformation() {
try {
const userName =
this.authService.getUserPermission('userName') ||
this.authService.getUserPermission('name');
const userEmail =
this.authService.getUserPermission('userEmail') ||
this.authService.getUserPermission('email');
if (userName) {
const nameInput = document.getElementById('contact-name');
if (nameInput && !nameInput.value) {
nameInput.value = userName;
}
}
if (userEmail) {
const emailInput = document.getElementById('contact-email');
if (emailInput && !emailInput.value) {
emailInput.value = userEmail;
}
}
} catch (error) {
console.warn('Failed to prefill user information:', error);
}
}
/**
* Show authenticated user options
*/
showAuthenticatedOptions() {
try {
const form = document.getElementById('contact-form');
if (form) {
const notice = document.createElement('div');
notice.className = 'bsp-auth-notice';
notice.innerHTML = `
<div class="bsp-alert bsp-alert-info" style="background: #dbeafe; border: 1px solid #3b82f6; color: #1e40af; padding: 0.75rem; border-radius: 6px; font-size: 0.85rem; margin-bottom: 1rem;">
✓ You are signed in. Your message will be linked to your account for faster response.
</div>
`;
form.insertAdjacentElement('afterbegin', notice);
}
} catch (error) {
console.warn('Failed to show authenticated options:', error);
}
}
/**
* Handle URL parameters for pre-selection
*/
handleUrlParameters() {
try {
const urlParams = new URLSearchParams(window.location.search);
const request = urlParams.get('request');
if (request) {
const subjectSelect = document.getElementById('contact-subject');
if (subjectSelect) {
switch (request) {
case 'presentation':
subjectSelect.value = 'presentation';
break;
case 'demo':
subjectSelect.value = 'demo';
break;
case 'sales':
subjectSelect.value = 'sales';
break;
default:
subjectSelect.value = 'general';
}
}
// Pre-fill message based on request type
const messageTextarea = document.getElementById('contact-message');
if (messageTextarea && !messageTextarea.value) {
const messages = {
presentation:
'Hi, I would like to request a company presentation. Please let me know when we can schedule a meeting.',
demo: "Hi, I'm interested in seeing a live demo of B-esper. Could we schedule a demonstration session?",
sales:
"Hi, I'd like to learn more about B-esper's pricing and how it can help our organization.",
};
if (messages[request]) {
messageTextarea.value = messages[request];
}
}
}
} catch (error) {
console.warn('Failed to handle URL parameters:', error);
}
}
/**
* Pre-fill form with provided data
*/
prefillForm(formData) {
try {
const fields = ['name', 'email', 'company', 'subject', 'message'];
fields.forEach(field => {
const element = document.getElementById(`contact-${field}`);
if (element && formData[field]) {
element.value = formData[field];
}
});
} catch (error) {
console.warn('Failed to prefill form:', error);
}
}
/**
* Initialize form handling
*/
initializeFormHandling() {
// Add form submission handler
this.form.addEventListener('submit', this.handleFormSubmit.bind(this));
// Add real-time validation
const inputs = this.form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('blur', () => this.validateField(input));
input.addEventListener('input', () => this.clearFieldError(input));
});
// Email validation on input
const emailInput = document.getElementById('contact-email');
if (emailInput) {
emailInput.addEventListener('input', () =>
this.validateEmail(emailInput)
);
}
}
/**
* Handle form submission
*/
async handleFormSubmit(e) {
e.preventDefault();
try {
// Validate all fields
if (!this.validateForm()) {
return;
}
// Show loading state
this.setSubmitState('loading');
this.clearMessages();
// Collect form data
const formData = this.collectFormData();
// Submit as customer outreach
const response = await this.submitCustomerOutreach(formData);
if (response.success) {
this.showSuccessMessage(response);
this.resetForm();
} else {
throw new Error(response.message || 'Submission failed');
}
} catch (error) {
console.error('Form submission error:', error);
this.showErrorMessage(error.message);
} finally {
this.setSubmitState('normal');
}
}
/**
* Validate entire form
*/
validateForm() {
let isValid = true;
const requiredFields = [
'contact-name',
'contact-email',
'contact-subject',
'contact-message',
];
requiredFields.forEach(fieldId => {
const field = document.getElementById(fieldId);
if (!this.validateField(field)) {
isValid = false;
}
});
// Validate consent checkbox
const consentCheckbox = document.getElementById('contact-consent');
if (!consentCheckbox.checked) {
this.showFieldError(
consentCheckbox,
'You must agree to the terms and conditions to continue'
);
isValid = false;
}
return isValid;
}
/**
* Validate individual field
*/
validateField(field) {
if (!field) return true;
this.clearFieldError(field);
// Required field validation
if (field.hasAttribute('required') && !field.value.trim()) {
this.showFieldError(field, 'This field is required');
return false;
}
// Email validation
if (field.type === 'email' && field.value.trim()) {
return this.validateEmail(field);
}
// Subject validation
if (field.id === 'contact-subject' && field.value === '') {
this.showFieldError(field, 'Please select a subject');
return false;
}
// Message length validation
if (field.id === 'contact-message' && field.value.trim().length < 10) {
this.showFieldError(
field,
'Please provide a more detailed message (at least 10 characters)'
);
return false;
}
// Mark as valid
field.classList.add('bsp-form-success');
return true;
}
/**
* Validate email field
*/
validateEmail(emailField) {
const email = emailField.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email && !emailRegex.test(email)) {
this.showFieldError(emailField, 'Please enter a valid email address');
return false;
}
if (email) {
emailField.classList.add('bsp-form-success');
}
return true;
}
/**
* Show field error
*/
showFieldError(field, message) {
field.classList.add('bsp-form-error');
field.classList.remove('bsp-form-success');
// Remove existing error message
const existingError = field.parentNode.querySelector(
'.bsp-form-error-message'
);
if (existingError) {
existingError.remove();
}
// Add new error message
const errorDiv = document.createElement('div');
errorDiv.className = 'bsp-form-error-message';
errorDiv.textContent = message;
field.parentNode.appendChild(errorDiv);
}
/**
* Clear field error
*/
clearFieldError(field) {
field.classList.remove('bsp-form-error');
const errorMessage = field.parentNode.querySelector(
'.bsp-form-error-message'
);
if (errorMessage) {
errorMessage.remove();
}
}
/**
* Collect form data
*/
collectFormData() {
const formData = new FormData(this.form);
const data = {};
for (const [key, value] of formData.entries()) {
data[key] = value;
}
// Add metadata
data.timestamp = new Date().toISOString();
data.source = 'website_contact_form';
data.page_url = window.location.href;
return data;
}
/**
* Submit customer outreach to APIM (creates document in customer outreach container)
* Uses the same CORS configuration as working bot operations API
*/
async submitCustomerOutreach(formData) {
try {
// Prepare customer outreach document for APIM matching the API spec
const customerOutreachData = {
email: formData.email,
message: formData.message,
category: formData.subject || 'general_inquiry',
datetime: new Date().toISOString(), // Required field per API spec
contacted: false, // New boolean field for tracking contact status
responded: false, // New boolean field for tracking response status
creator_user_id: this.isAuthenticated
? this.authService.getUserPermission('contactId')
: null,
};
// Add additional metadata for internal tracking
customerOutreachData.metadata = {
name: formData.name,
company: formData.company || null,
source: 'website_contact_form',
page_url: window.location.href,
authenticated: this.isAuthenticated,
workspace_id: this.isAuthenticated
? this.authService.getUserPermission('workspaceId')
: null,
account_id: this.isAuthenticated
? this.authService.getUserPermission('accountId')
: null,
};
// Use the EXACT same headers and configuration as working bot operations API
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
};
// Add authorization header if authenticated
if (this.isAuthenticated) {
headers.Authorization = `Bearer ${this.authService.getToken()}`;
}
// Use the EXACT same fetch configuration as the working APIs
const response = await fetch(
`${this.getRootApiEndpoint()}/customer-outreach/create`,
{
method: 'POST',
headers,
body: JSON.stringify(customerOutreachData),
}
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`HTTP ${response.status}: ${response.statusText} - ${errorText}`
);
}
const result = await response.json();
return {
success: true,
data: result,
message:
"Thank you for contacting us. We'll get back to you within 24 hours.",
};
} catch (error) {
console.error('Customer outreach submission error:', error);
// More specific error handling for different error types
let errorMessage =
'Unable to submit form. Please try again or contact us directly.';
if (error.message.includes('Failed to fetch')) {
errorMessage =
'Network error. Please check your connection and try again.';
} else if (error.message.includes('CORS')) {
errorMessage = 'Configuration error. Please contact support.';
} else if (error.message.includes('401')) {
errorMessage = 'Authentication required. Please log in and try again.';
} else if (error.message.includes('403')) {
errorMessage = 'Access denied. Please contact support.';
}
return {
success: false,
message: errorMessage,
};
}
}
/**
* Set submit button state
*/
setSubmitState(state) {
if (!this.submitButton) return;
switch (state) {
case 'loading':
this.submitButton.disabled = true;
this.submitButton.classList.add('bsp-btn-loading');
this.submitButton.innerHTML = `
<svg class="bsp-icon-sm" viewBox="0 0 24 24" fill="currentColor" style="animation: spin 1s linear infinite;">
<path d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg>
Sending...
`;
break;
case 'normal':
default:
this.submitButton.disabled = false;
this.submitButton.classList.remove('bsp-btn-loading');
this.submitButton.innerHTML = `
<svg class="bsp-icon-sm" viewBox="0 0 24 24" fill="currentColor">
<path d="M2,21L23,12L2,3V10L17,12L2,14V21Z" />
</svg>
Send Message
`;
break;
}
}
/**
* Show success message
*/
showSuccessMessage(_result = {}) {
this.messagesContainer.innerHTML = `
<div class="bsp-form-message-success">
<h4 style="margin: 0 0 8px 0;">Message Sent Successfully!</h4>
<p style="margin: 0;">Thank you for contacting us. We'll get back to you within 24 hours.</p>
</div>
`;
// Scroll to message
this.messagesContainer.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
/**
* Show error message
*/
showErrorMessage(message) {
this.messagesContainer.innerHTML = `
<div class="bsp-form-message-error">
<h4 style="margin: 0 0 8px 0;">Submission Failed</h4>
<p style="margin: 0;">${message}</p>
<p style="margin: 8px 0 0 0;"><small>You can also email us directly at <a href="mailto:info@b-esper.com">info@b-esper.com</a></small></p>
</div>
`;
// Scroll to message
this.messagesContainer.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
/**
* Clear messages
*/
clearMessages() {
this.messagesContainer.innerHTML = '';
}
/**
* Reset form
*/
resetForm() {
this.form.reset();
// Clear all validation states
const fields = this.form.querySelectorAll('input, select, textarea');
fields.forEach(field => {
field.classList.remove('bsp-form-error', 'bsp-form-success');
});
// Clear error messages
const errorMessages = this.form.querySelectorAll('.bsp-form-error-message');
errorMessages.forEach(msg => msg.remove());
}
/**
* Show error message
*/
showError(_error) {
const container = document.getElementById(this.options.containerId);
if (container) {
container.innerHTML = `
<div class="bsp-alert bsp-alert-error">
<h3>Error Loading Contact Page</h3>
<p>Sorry, there was a problem loading the contact page. Please try again later.</p>
<p>You can reach us directly at <a href="mailto:info@b-esper.com">info@b-esper.com</a></p>
<button class="bsp-btn bsp-btn-outline" onclick="location.reload()">
Retry
</button>
</div>
`;
}
}
/**
* Cleanup resources
*/
destroy() {
if (this.form) {
this.form.removeEventListener('submit', this.handleFormSubmit);
}
this.initialized = false;
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = ContactUsPage;
} else if (typeof window !== 'undefined') {
window.ContactUsPage = ContactUsPage;
}