diff --git a/assets/css/browser-phone-frontend.css b/assets/css/browser-phone-frontend.css
index 31a121a..474a3db 100644
--- a/assets/css/browser-phone-frontend.css
+++ b/assets/css/browser-phone-frontend.css
@@ -434,10 +434,33 @@
.queue-actions {
display: flex;
gap: 8px;
+ flex-wrap: wrap;
}
.queue-actions .twp-btn {
flex: 1;
+ min-width: 80px;
+}
+
+/* Alert Toggle Button */
+#twp-alert-toggle {
+ position: relative;
+}
+
+#twp-alert-toggle.alert-on {
+ background-color: #28a745 !important;
+ border-color: #28a745;
+ color: white;
+}
+
+#twp-alert-toggle.alert-off {
+ background-color: #6c757d !important;
+ border-color: #6c757d;
+ color: white;
+}
+
+#twp-alert-toggle:hover {
+ opacity: 0.8;
}
.twp-btn-secondary {
diff --git a/assets/js/browser-phone-frontend.js b/assets/js/browser-phone-frontend.js
index 83e66d2..1fecf22 100644
--- a/assets/js/browser-phone-frontend.js
+++ b/assets/js/browser-phone-frontend.js
@@ -15,6 +15,11 @@
let selectedQueue = null;
let tokenRefreshTimer = null;
let tokenExpiry = null;
+ let queuePollingTimer = null;
+ let lastQueueUpdate = {};
+ let alertSound = null;
+ let alertInterval = null;
+ let alertEnabled = false;
// Initialize when document is ready
$(document).ready(function() {
@@ -263,6 +268,7 @@
// Accept queue call button
$('#twp-accept-queue-call').on('click', function() {
acceptQueueCall();
+ stopAlert(); // Stop alert when accepting call
});
// Refresh queues button
@@ -270,6 +276,11 @@
loadUserQueues();
});
+ // Alert toggle button
+ $(document).on('click', '#twp-alert-toggle', function() {
+ toggleAlert();
+ });
+
// Queue item selection
$(document).on('click', '.queue-item', function() {
const queueId = $(this).data('queue-id');
@@ -313,6 +324,9 @@
return;
}
+ // Stop alerts when making a call
+ stopAlert();
+
updateCallState('connecting');
showCallInfo('Connecting...');
@@ -420,12 +434,20 @@
updateCallState('idle');
hideCallInfo();
$('.twp-browser-phone-container').removeClass('incoming-call');
+
+ // Restart alerts if enabled and there are waiting calls
+ if (alertEnabled) {
+ const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0);
+ if (hasWaitingCalls) {
+ setTimeout(startAlert, 1000); // Small delay to avoid immediate alert
+ }
+ }
}
/**
* Load user's assigned queues
*/
- function loadUserQueues() {
+ function loadUserQueues(silent = false) {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
@@ -435,18 +457,44 @@
},
success: function(response) {
if (response.success) {
+ // Check for new calls in queues
+ checkForNewCalls(response.data);
userQueues = response.data;
displayQueues();
- } else {
+ } else if (!silent) {
showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
- showMessage('Failed to load queues', 'error');
+ if (!silent) {
+ showMessage('Failed to load queues', 'error');
+ }
}
});
}
+ /**
+ * Check for new calls in queues and trigger alerts
+ */
+ function checkForNewCalls(newQueues) {
+ newQueues.forEach(function(queue) {
+ const queueId = queue.id;
+ const currentWaiting = parseInt(queue.current_waiting) || 0;
+ const previousWaiting = lastQueueUpdate[queueId] || 0;
+
+ // If waiting count increased, we have new calls
+ if (currentWaiting > previousWaiting) {
+ console.log('New call detected in queue:', queue.queue_name);
+ // Trigger alert if enabled
+ if (alertEnabled && !currentCall) {
+ startAlert();
+ }
+ }
+
+ lastQueueUpdate[queueId] = currentWaiting;
+ });
+ }
+
/**
* Display queues in the UI
*/
@@ -653,16 +701,29 @@
}
}
- // Periodic status updates
- setInterval(function() {
- if (isConnected) {
- loadUserQueues(); // This will refresh all queue data including waiting counts
+ // Start queue polling with faster interval
+ startQueuePolling();
+
+ /**
+ * Start polling for queue updates
+ */
+ function startQueuePolling() {
+ // Clear any existing timer
+ if (queuePollingTimer) {
+ clearInterval(queuePollingTimer);
}
- }, 30000); // Every 30 seconds
+
+ // Poll every 5 seconds for real-time updates
+ queuePollingTimer = setInterval(function() {
+ if (isConnected) {
+ loadUserQueues(true); // Silent update
+ }
+ }, 5000); // Every 5 seconds
+ }
/**
* Schedule token refresh
- * Refreshes token 5 minutes before expiry
+ * Refreshes token 10 minutes before expiry for safety
*/
function scheduleTokenRefresh() {
// Clear any existing timer
@@ -672,11 +733,17 @@
if (!tokenExpiry) {
console.error('Token expiry time not set');
+ // Retry in 30 seconds if token expiry not set
+ setTimeout(function() {
+ if (tokenExpiry) {
+ scheduleTokenRefresh();
+ }
+ }, 30000);
return;
}
- // Calculate time until refresh (5 minutes before expiry)
- const refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds
+ // Calculate time until refresh (10 minutes before expiry for extra safety)
+ const refreshBuffer = 10 * 60 * 1000; // 10 minutes in milliseconds
const timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer;
if (timeUntilRefresh <= 0) {
@@ -734,14 +801,127 @@
});
}
+ /**
+ * Initialize alert sound
+ */
+ function initAlertSound() {
+ // Create audio element for alert sound
+ alertSound = new Audio();
+ alertSound.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA='; // Simple beep sound
+ // Use Web Audio API for better sound
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+ // Create a simple beep sound
+ function playBeep() {
+ const oscillator = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+
+ oscillator.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+
+ oscillator.frequency.value = 800; // Frequency in Hz
+ gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
+
+ oscillator.start(audioContext.currentTime);
+ oscillator.stop(audioContext.currentTime + 0.5);
+ }
+
+ return playBeep;
+ }
+
+ const playAlertSound = initAlertSound();
+
+ /**
+ * Start alert for new calls
+ */
+ function startAlert() {
+ if (!alertEnabled || alertInterval) return;
+
+ // Play initial alert
+ playAlertSound();
+
+ // Repeat every 30 seconds
+ alertInterval = setInterval(function() {
+ if (alertEnabled && !currentCall) {
+ playAlertSound();
+ } else {
+ stopAlert();
+ }
+ }, 30000);
+ }
+
+ /**
+ * Stop alert
+ */
+ function stopAlert() {
+ if (alertInterval) {
+ clearInterval(alertInterval);
+ alertInterval = null;
+ }
+ }
+
+ /**
+ * Toggle alert on/off
+ */
+ function toggleAlert() {
+ alertEnabled = !alertEnabled;
+ localStorage.setItem('twp_alert_enabled', alertEnabled);
+
+ // Update button state
+ updateAlertButton();
+
+ if (!alertEnabled) {
+ stopAlert();
+ showMessage('Queue alerts disabled', 'info');
+ } else {
+ showMessage('Queue alerts enabled', 'success');
+ // Check if there are waiting calls
+ const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0);
+ if (hasWaitingCalls && !currentCall) {
+ startAlert();
+ }
+ }
+ }
+
+ /**
+ * Update alert button UI
+ */
+ function updateAlertButton() {
+ const $btn = $('#twp-alert-toggle');
+ if (alertEnabled) {
+ $btn.removeClass('alert-off').addClass('alert-on').html('🔔 Alerts ON');
+ } else {
+ $btn.removeClass('alert-on').addClass('alert-off').html('🔕 Alerts OFF');
+ }
+ }
+
+ /**
+ * Load alert preference from localStorage
+ */
+ function loadAlertPreference() {
+ const saved = localStorage.getItem('twp_alert_enabled');
+ alertEnabled = saved === null ? true : saved === 'true';
+ updateAlertButton();
+ }
+
// Clean up on page unload
$(window).on('beforeunload', function() {
if (tokenRefreshTimer) {
clearTimeout(tokenRefreshTimer);
}
+ if (queuePollingTimer) {
+ clearInterval(queuePollingTimer);
+ }
+ if (alertInterval) {
+ clearInterval(alertInterval);
+ }
if (twilioDevice) {
twilioDevice.destroy();
}
});
+ // Load alert preference on init
+ loadAlertPreference();
+
})(jQuery);
\ No newline at end of file
diff --git a/debug-phone-numbers.php b/debug-phone-numbers.php
deleted file mode 100644
index 77ad3dd..0000000
--- a/debug-phone-numbers.php
+++ /dev/null
@@ -1,83 +0,0 @@
-incomingPhoneNumbers->read([], 10);
-
- if (empty($numbers)) {
- echo "No phone numbers found in your Twilio account.\n";
- exit(0);
- }
-
- echo "Found " . count($numbers) . " phone number(s):\n\n";
-
- foreach ($numbers as $i => $number) {
- echo "=== Phone Number " . ($i + 1) . " ===\n";
- echo "SID: " . $number->sid . "\n";
- echo "Phone Number: " . $number->phoneNumber . "\n";
- echo "Friendly Name: " . ($number->friendlyName ?: '[Not set]') . "\n";
- echo "Voice URL: " . ($number->voiceUrl ?: '[Not set]') . "\n";
- echo "SMS URL: " . ($number->smsUrl ?: '[Not set]') . "\n";
- echo "Account SID: " . $number->accountSid . "\n";
-
- // Debug capabilities object
- echo "\nCapabilities (raw object):\n";
- var_dump($number->capabilities);
-
- echo "\nCapabilities (properties):\n";
- echo "- Voice: " . ($number->capabilities->voice ? 'YES' : 'NO') . "\n";
- echo "- SMS: " . ($number->capabilities->sms ? 'YES' : 'NO') . "\n";
- echo "- MMS: " . ($number->capabilities->mms ? 'YES' : 'NO') . "\n";
- echo "\n" . str_repeat('-', 40) . "\n\n";
- }
-
-} catch (Exception $e) {
- echo "ERROR: " . $e->getMessage() . "\n";
- echo "Class: " . get_class($e) . "\n";
- exit(1);
-}
-
-echo "Debug complete!\n";
\ No newline at end of file
diff --git a/includes/class-twp-notifications.php b/includes/class-twp-notifications.php
index 8558bd5..4218dfa 100644
--- a/includes/class-twp-notifications.php
+++ b/includes/class-twp-notifications.php
@@ -347,12 +347,14 @@ class TWP_Notifications {
* Send webhook request
*/
private static function send_webhook_request($webhook_url, $payload, $service) {
+ // Send notification immediately without blocking
$response = wp_remote_post($webhook_url, array(
'headers' => array(
'Content-Type' => 'application/json',
),
'body' => json_encode($payload),
- 'timeout' => 30,
+ 'timeout' => 10, // Reduce timeout for faster processing
+ 'blocking' => false, // Non-blocking request for immediate processing
));
if (is_wp_error($response)) {
@@ -360,15 +362,9 @@ class TWP_Notifications {
return false;
}
- $response_code = wp_remote_retrieve_response_code($response);
- if ($response_code >= 200 && $response_code < 300) {
- error_log("TWP {$service} notification sent successfully");
- return true;
- } else {
- error_log("TWP {$service} notification failed with response code: " . $response_code);
- error_log("Response body: " . wp_remote_retrieve_body($response));
- return false;
- }
+ // For non-blocking requests, we can't check response code immediately
+ error_log("TWP {$service} notification sent (non-blocking)");
+ return true;
}
/**
diff --git a/includes/class-twp-shortcodes.php b/includes/class-twp-shortcodes.php
index feea462..b92f09a 100644
--- a/includes/class-twp-shortcodes.php
+++ b/includes/class-twp-shortcodes.php
@@ -193,6 +193,9 @@ class TWP_Shortcodes {
+