class IntegrationsManager { constructor() { this.initElements(); this.initListeners(); this.init(); } initElements() { //form.integrations //data-action //state this.selectors = { form: 'form.integration', action: 'data-action', }; let forms = document.querySelectorAll(this.selectors.form); this.forms = new Map(); forms.forEach(form => { this.forms.set(form.dataset.service, form); }); } initListeners() { this.handleClick = this.clickHandler.bind(this); this.handleChange = this.changeHandler.bind(this); this.handleSubmit = this.submitHandler.bind(this); // if (this.forms.size > 0) { // for (const form of this.forms.values()) { // form.addEventListener('click', this.handleClick); // form.addEventListener('change', this.handleChange); // form.addEventListener('submit', this.handleSubmit); // } // } document.addEventListener('click', this.handleClick); document.addEventListener('change', this.handleChange); document.addEventListener('submit', this.handleSubmit); } init() { document.addEventListener('DOMContentLoaded', () => { this.checkForOAuthMessages(); }); } /** * Check URL parameters for OAuth success/error messages */ checkForOAuthMessages() { const urlParams = new URLSearchParams(window.location.search); const success = urlParams.get('success'); const error = urlParams.get('error'); if (success) { this.showNotification(success, 'success'); // Clean URL without reloading this.cleanURL(); } else if (error) { this.showNotification(error, 'error'); // Clean URL without reloading this.cleanURL(); } } /** * Remove query parameters from URL without reloading */ cleanURL() { const url = new URL(window.location); url.searchParams.delete('success'); url.searchParams.delete('error'); window.history.replaceState({}, document.title, url.pathname + url.hash); } /** * Show notification message */ showNotification(message, type = 'info') { // If you have a notification system, use it if (this.popup) { this.addPopup(message, type === 'error' ? 5000 : 3000); } else { // Fallback to console or alert console.log(`[${type}]`, message); // Update any status elements on the page const statusElements = document.querySelectorAll('.integration-status-message'); statusElements.forEach(el => { el.textContent = message; el.className = `integration-status-message ${type}`; // Auto-hide after delay setTimeout(() => { el.textContent = ''; el.className = 'integration-status-message'; }, 5000); }); } } addPopup(message, delay = 2000) { if (!this.popup) { // Create popup element if it doesn't exist this.popup = document.querySelector('.integration-popup') || this.createPopupElement(); } this.popup.textContent = message; this.popup.classList.add('showing'); setTimeout(() => { this.popup.classList.remove('showing'); }, delay); } createPopupElement() { const popup = document.createElement('div'); popup.className = 'integration-popup'; document.body.appendChild(popup); return popup; } clickHandler(e) { if (!e.target.closest(this.selectors.form)) { return; } console.log('Clicked!'); if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { e.preventDefault(); let target = e.target.tagName === 'BUTTON' ? e.target : e.target.closest('button'); this.handleAction(target); } } changeHandler(e) { if (!e.target.closest(this.selectors.form)) { return; } if ('action' in e.target.dataset) { this.handleAction(e.target); } else { let form = this.getFormFromTarget(e.target); if (!form) { return; } form.classList.add('hasChanges'); form.querySelector('.setup .text').textContent = 'Unsaved Changes'; } } submitHandler(e) { if (!e.target.closest(this.selectors.form)) { return; } e.preventDefault(); } getFormFromTarget(target) { let service = target.closest('form')?.dataset.service; return this.forms.get(service)??false; } async handleAction(input) { const form = input.closest('form'); const service = form.dataset.service; const action = input.dataset.action; const isButton = input.tagName === 'BUTTON'; const isSave = isButton && action === 'save_credentials'; if ('confirm' in input.dataset && !confirm(input.dataset.confirm)) { return; } this.updateUI(form, 'syncing'); try { this.updateUI(form, 'syncing'); const data = { service: service, action: action, user_id: jvbSettings.currentUser, data: {} }; if (!isButton) { data.data[input.name.replace(service+'_', '')] = input.value; } if (isSave) { const formData = new FormData(form); for (let [key, value] of formData.entries()) { if (!['service'].includes(key) && !key.includes('nonce')) { data.data[key.replace(service+'_', '')] = value; } } } console.log('Sending Data:', data); // Make API request const response = await fetch( jvbSettings.api + 'integrations', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': jvbSettings.nonce }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok && result.success) { let status = 'connected'; switch (action) { case 'clear_credentials': status = 'disconnected'; break; case 'save_credentials': this.showNotification('Settings saved successfully', 'success'); break; } console.log(result); this.updateUI(form, status); if (result.reload) { setTimeout(() => { window.location.reload(); }, 50); } } else { console.log (result); this.updateUI(form, 'error', result.message??''); this.showNotification(result.message || 'Operation failed', 'error'); } } catch (error) { this.updateUI(form, 'error'); this.showNotification('Network error: ' + error.message, 'error'); console.error('API Error:', error); } } updateUI(form, state, message = '') { let allowed = ['connected', 'disconnected', 'hasChanges', 'syncing', 'error']; if (!allowed.includes(state)) { console.log('Invalid state: ', state); return; } let defaults = { connected: 'Set Up', disconnected: 'Not Set Up', hasChanges: 'Unsaved Changes', syncing: 'Testing changes', error: 'Something went wrong' }; message = message === '' ? defaults[state] : message; if (state === 'syncing') { form.querySelectorAll('button').forEach(button => { button.disabled = true; }); } else { form.querySelectorAll('button[disabled]').forEach(button => { button.disabled = false; }); } form.classList.remove(...allowed); form.classList.add(state, 'flash'); console.log(form); let status = form.querySelector('.setup .text'); console.log(status); status.textContent = message; // Enable/disable buttons if (state === 'syncing') { form.querySelectorAll('button').forEach(btn => btn.disabled = true); } else { form.querySelectorAll('button:disabled').forEach(btn => btn.disabled = false); } setTimeout(() => form.classList.remove('flash'), 600); } } window.jvbOAuthPopup = function(url, service) { const width = 600; const height = 700; const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; // Add popup indicator to URL url += (url.indexOf('?') > -1 ? '&' : '?') + 'popup=1'; console.log('Opening OAuth popup for', service, 'with URL:', url); const popup = window.open( url, service + '-oauth', `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes,toolbar=no,menubar=no` ); if (!popup) { alert('Please allow popups for this site to complete the authorization process.'); return false; } // Set up listener for OAuth completion window.jvbOAuthComplete = function(completedService, success, message) { console.log('OAuth complete:', completedService, success, message); if (completedService === service) { if (success) { // Show success message const statusEl = document.querySelector(`.integration-card[data-service="${service}"] .setup .text`); if (statusEl) { statusEl.textContent = 'Connection successful! Refreshing...'; } // Refresh the integration card setTimeout(() => { jvbRefreshIntegration(service); }, 1000); } else { // Show error message alert('OAuth authorization failed: ' + (message || 'Unknown error')); // Still refresh to update UI state jvbRefreshIntegration(service); } } }; // Fallback: Check if popup closed manually const checkPopup = setInterval(() => { try { if (popup.closed) { clearInterval(checkPopup); console.log('OAuth popup closed'); // Refresh anyway in case auth completed setTimeout(() => { jvbRefreshIntegration(service); }, 1000); } } catch (e) { // Cross-origin error, popup is still open } }, 1000); return false; }; // Refresh integration display window.jvbRefreshIntegration = function(service) { console.log('Refreshing integration:', service); // Use your REST API to check connection status fetch(jvbSettings.api + 'integrations', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': jvbSettings.nonce }, body: JSON.stringify({ service: service, action: 'check_oauth_status' }) }) .then(response => response.json()) .then(data => { console.log('OAuth status check result:', data); if (data.success && data.authorized) { // Update UI to show connected state const card = document.querySelector(`.integration-card[data-service="${service}"]`); if (card) { card.classList.remove('disconnected'); card.classList.add('connected'); const statusEl = card.querySelector('.setup .text'); if (statusEl) { statusEl.textContent = 'Connected'; } // Show success message then reload setTimeout(() => { location.reload(); }, 1500); } } else { // Just reload to update the UI location.reload(); } }) .catch(error => { console.error('Error checking OAuth status:', error); // Reload anyway location.reload(); }); }; window.integrations = new IntegrationsManager();