UNPKG

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