From 42fa8304ddb811b0f725f245130f70c0f5e86a6c Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 04 Nov 2025 06:12:02 +0000
Subject: [PATCH] =Refactored LoginManager to be more extensible and configurable, as well as an AjaxRateLimiter

---
 assets/js/dash/Integrations.js |  164 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 143 insertions(+), 21 deletions(-)

diff --git a/assets/js/dash/Integrations.js b/assets/js/dash/Integrations.js
index 079afcc..e0d9ed6 100644
--- a/assets/js/dash/Integrations.js
+++ b/assets/js/dash/Integrations.js
@@ -53,11 +53,21 @@
 		const error = urlParams.get('error');
 
 		if (success) {
-			this.showNotification(success, 'success');
+			this.showNotification(success, 'success', 5000);
+
 			// Clean URL without reloading
 			this.cleanURL();
+
+			// Refresh the integration status display
+			const forms = document.querySelectorAll('form.integration');
+			forms.forEach(form => {
+				// Update UI to show connected state
+				this.updateUI(form, 'connected');
+			});
+
 		} else if (error) {
-			this.showNotification(error, 'error');
+			this.showNotification(error, 'error', 8000);
+
 			// Clean URL without reloading
 			this.cleanURL();
 		}
@@ -76,26 +86,42 @@
 	/**
 	 * Show notification message
 	 */
-	showNotification(message, type = 'info') {
-		// If you have a notification system, use it
+	showNotification(message, type = 'info', duration = 5000) {
+		// Find or create notification container
+		let container = document.querySelector('.integration-status-message');
+
+		if (!container) {
+			// Create notification container
+			container = document.createElement('div');
+			container.className = 'integration-status-message';
+
+			// Insert at top of main content or integrations container
+			const target = document.querySelector('.integration-settings') ||
+				document.querySelector('main') ||
+				document.body;
+			target.insertBefore(container, target.firstChild);
+		}
+
+		// Update content and type
+		container.textContent = message;
+		container.className = `integration-status-message ${type}`;
+
+		// Clear any existing timeout
+		if (this.notificationTimeout) {
+			clearTimeout(this.notificationTimeout);
+		}
+
+		// Auto-hide after duration
+		if (duration > 0) {
+			this.notificationTimeout = setTimeout(() => {
+				container.className = 'integration-status-message';
+				container.textContent = '';
+			}, duration);
+		}
+
+		// Also use popup if available
 		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);
-			});
+			this.addPopup(message, duration);
 		}
 	}
 
@@ -122,9 +148,21 @@
 	}
 
 	clickHandler(e) {
+		// // Check for OAuth authorization link
+		// if (e.target.classList.contains('jvb-oauth-connect') ||
+		// 	e.target.closest('.jvb-oauth-connect')) {
+		// 	e.preventDefault();
+		// 	const link = e.target.classList.contains('jvb-oauth-connect')
+		// 		? e.target
+		// 		: e.target.closest('.jvb-oauth-connect');
+		// 	return this.handleOAuthClick(link);
+		// }
+
+		// Existing integration form handling
 		if (!e.target.closest(this.selectors.form)) {
 			return;
 		}
+
 		console.log('Clicked!');
 		if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
 			e.preventDefault();
@@ -159,6 +197,90 @@
 		return this.forms.get(service)??false;
 	}
 
+	/**
+	 * Handle OAuth authorization link clicks
+	 * Opens OAuth in a popup window with proper handling
+	 */
+	handleOAuthClick(link) {
+		const service = link.dataset.service;
+		const href = link.href;
+
+		// Calculate center position for popup
+		const width = 600;
+		const height = 700;
+		const left = (screen.width - width) / 2;
+		const top = (screen.height - height) / 2;
+
+		// Show loading notification
+		this.showNotification('Opening authorization window...', 'info');
+
+		// Add loading state to the link
+		link.classList.add('loading');
+		link.setAttribute('aria-busy', 'true');
+
+		// Open OAuth in popup
+		const popup = window.open(
+			href,
+			'oauth_' + service,
+			`width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no,location=yes,status=yes,resizable=yes`
+		);
+
+		if (!popup) {
+			// Popup was blocked
+			this.showNotification('Popup was blocked. Please allow popups and try again.', 'error');
+			link.classList.remove('loading');
+			link.removeAttribute('aria-busy');
+			return true; // Allow default behavior as fallback
+		}
+
+		// Focus the popup
+		popup.focus();
+
+		// Update notification
+		this.showNotification('Waiting for authorization...', 'info');
+
+		// Poll for popup close
+		const pollTimer = setInterval(() => {
+			try {
+				if (popup.closed) {
+					clearInterval(pollTimer);
+
+					// Remove loading state
+					link.classList.remove('loading');
+					link.removeAttribute('aria-busy');
+
+					// Show checking notification
+					this.showNotification('Checking authorization status...', 'info');
+
+					// Wait a moment for redirect to complete, then check for messages
+					setTimeout(() => {
+						this.checkForOAuthMessages();
+
+						// If no messages found, reload to get updated connection status
+						setTimeout(() => {
+							const urlParams = new URLSearchParams(window.location.search);
+							if (!urlParams.has('success') && !urlParams.has('error')) {
+								// No messages in URL, reload to check server-side status
+								window.location.reload();
+							}
+						}, 500);
+					}, 500);
+				}
+			} catch (error) {
+				// Ignore cross-origin errors during polling
+			}
+		}, 500);
+
+		// Safety timeout - stop polling after 5 minutes
+		setTimeout(() => {
+			clearInterval(pollTimer);
+			link.classList.remove('loading');
+			link.removeAttribute('aria-busy');
+		}, 300000);
+
+		return false; // Prevent default link behavior
+	}
+
 	async handleAction(input) {
 		const form = input.closest('form');
 		const service = form.dataset.service;

--
Gitblit v1.10.0