Files
twilio-wp-plugin/assets/js/browser-phone-frontend.js
jknapp e6e177a114 Improve call recording functionality with better debugging and Call SID detection
## Recording Issues Fixed
- Enhanced Call SID detection for browser phone calls
- Try multiple methods to get Call SID: parameters.CallSid, customParameters.CallSid, outgoingConnectionId, sid
- Added comprehensive console logging for debugging recording issues
- Better error handling and user feedback

## Backend Improvements
- Verify call exists and is in-progress before attempting recording
- Enhanced error logging with call status and details
- Improved AJAX error responses with specific error messages
- Database insert error checking and logging

## Frontend Improvements
- Multiple Call SID detection methods for browser phone compatibility
- Console logging of call object and Call SID for debugging
- Enhanced error handling with xhr response details
- Validation of active call before recording attempt

## Debugging Features
- Detailed error logs in PHP error log
- Console logging in browser for JavaScript debugging
- Specific error messages for different failure scenarios
- Call status validation to ensure recording is possible

These changes should resolve the "recording not working" issue by properly detecting the Call SID for browser phone calls and providing detailed debugging information.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-30 15:49:31 -07:00

1987 lines
67 KiB
JavaScript

/**
* Frontend Browser Phone for TWP Plugin
* Mobile-friendly implementation
*/
(function($) {
'use strict';
let twilioDevice = null;
let currentCall = null;
let callTimer = null;
let callStartTime = null;
let isConnected = false;
let availableNumbers = [];
let userQueues = [];
let selectedQueue = null;
let tokenRefreshTimer = null;
let tokenExpiry = null;
let queuePollingTimer = null;
let lastQueueUpdate = {};
let alertSound = null;
let alertInterval = null;
let alertEnabled = false;
let notificationPermission = 'default';
let backgroundAlertInterval = null;
let isPageVisible = true;
let isOnHold = false;
let isRecording = false;
let recordingSid = null;
let personalQueueTimer = null;
let personalQueueName = null;
// Initialize when document is ready
$(document).ready(function() {
if (!twp_frontend_ajax.is_logged_in) {
showMessage('You must be logged in to use the browser phone.', 'error');
return;
}
initializeBrowserPhone();
bindEvents();
loadPhoneNumbers();
loadUserQueues();
initVoicemailSection();
initializeNotifications();
initializePageVisibility();
initializePersonalQueue();
});
/**
* Wait for Twilio SDK to load
*/
function waitForTwilioSDK(callback) {
let attempts = 0;
const maxAttempts = 150; // 15 seconds max (100ms * 150)
function checkTwilio() {
console.log('Checking Twilio SDK availability, attempt:', attempts + 1);
if (typeof Twilio !== 'undefined' && Twilio.Device) {
console.log('Twilio SDK loaded successfully');
callback();
return;
}
attempts++;
// Update status message periodically
if (attempts === 30) { // 3 seconds
updateStatus('connecting', 'Loading Twilio SDK...');
} else if (attempts === 60) { // 6 seconds
updateStatus('connecting', 'Still loading SDK...');
} else if (attempts === 100) { // 10 seconds
updateStatus('connecting', 'SDK taking longer than expected...');
}
if (attempts >= maxAttempts) {
console.error('Twilio SDK failed to load. Window.Twilio:', typeof Twilio);
updateStatus('offline', 'SDK load timeout');
showMessage('Twilio SDK failed to load within 15 seconds. This may be due to a slow connection or network restrictions. Please refresh the page and try again.', 'error');
return;
}
setTimeout(checkTwilio, 100);
}
checkTwilio();
}
/**
* Initialize the browser phone
*/
function initializeBrowserPhone() {
updateStatus('connecting', 'Initializing...');
// Wait for Twilio SDK to load, then initialize
waitForTwilioSDK(function() {
// Generate capability token
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_generate_capability_token',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
setupTwilioDevice(response.data.token);
// Set token expiry time (expires in 1 hour)
tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
scheduleTokenRefresh();
} else {
updateStatus('offline', 'Failed to initialize');
showMessage('Failed to initialize browser phone: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
updateStatus('offline', 'Connection failed');
showMessage('Failed to connect to server', 'error');
}
});
});
}
/**
* Setup Twilio Device
*/
async function setupTwilioDevice(token) {
// Check if Twilio SDK is loaded
if (typeof Twilio === 'undefined' || !Twilio.Device) {
updateStatus('offline', 'Twilio SDK not loaded');
showMessage('Twilio SDK failed to load. Please refresh the page.', 'error');
console.error('Twilio SDK is not available');
return;
}
try {
// If device already exists, destroy it first to prevent multiple connections
if (twilioDevice) {
twilioDevice.destroy();
twilioDevice = null;
}
twilioDevice = new Twilio.Device(token, {
logLevel: 1,
answerOnBridge: true
});
twilioDevice.on('registered', function() {
updateStatus('online', 'Ready');
isConnected = true;
// Only show success message on initial connection
if (!tokenRefreshTimer) {
showMessage('Browser phone ready!', 'success');
}
});
twilioDevice.on('error', function(error) {
console.error('Twilio Device Error:', error);
updateStatus('offline', 'Error: ' + error.message);
showMessage('Device error: ' + error.message, 'error');
// If token expired error, refresh immediately
if (error.message && error.message.toLowerCase().includes('token')) {
refreshToken();
}
});
twilioDevice.on('incoming', function(call) {
handleIncomingCall(call);
});
twilioDevice.on('disconnect', function() {
updateStatus('offline', 'Disconnected');
isConnected = false;
});
// Register device asynchronously
await twilioDevice.register();
} catch (error) {
console.error('Failed to setup Twilio Device:', error);
updateStatus('offline', 'Setup failed');
showMessage('Failed to setup device: ' + error.message, 'error');
}
}
/**
* Load available phone numbers
*/
function loadPhoneNumbers() {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_phone_numbers',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success && response.data) {
availableNumbers = response.data;
populateCallerIdSelect();
} else {
showMessage('Failed to load phone numbers', 'error');
}
},
error: function() {
showMessage('Failed to load phone numbers', 'error');
}
});
}
/**
* Populate caller ID select
*/
function populateCallerIdSelect() {
const $select = $('#twp-caller-id');
$select.empty();
if (availableNumbers.length === 0) {
$select.append('<option value="">No numbers available</option>');
return;
}
$select.append('<option value="">Select caller ID...</option>');
availableNumbers.forEach(function(number) {
const friendlyName = number.friendly_name || number.phone_number;
$select.append(`<option value="${number.phone_number}">${friendlyName} (${number.phone_number})</option>`);
});
}
/**
* Bind event handlers
*/
function bindEvents() {
// Dial pad buttons
$('.twp-dial-btn').on('click', function() {
const digit = $(this).data('digit');
addDigit(digit);
// Haptic feedback on mobile
if (navigator.vibrate) {
navigator.vibrate(50);
}
});
// Clear number button
$('#twp-clear-number').on('click', function() {
$('#twp-dial-number').val('');
});
// Call button
$('#twp-call-btn').on('click', function() {
if (!isConnected) {
showMessage('Device not connected', 'error');
return;
}
const number = $('#twp-dial-number').val().trim();
const callerId = $('#twp-caller-id').val();
if (!number) {
showMessage('Please enter a number to call', 'error');
return;
}
if (!callerId) {
showMessage('Please select a caller ID', 'error');
return;
}
makeCall(number, callerId);
});
// Hang up button
$('#twp-hangup-btn').on('click', function() {
hangupCall();
});
// Call control buttons
$('#twp-hold-btn').on('click', function() {
toggleHold();
});
$('#twp-transfer-btn').on('click', function() {
showTransferDialog();
});
$('#twp-requeue-btn').on('click', function() {
showRequeueDialog();
});
$('#twp-record-btn').on('click', function() {
toggleRecording();
});
// Transfer dialog handlers
$(document).on('click', '#twp-confirm-transfer', function() {
const agentNumber = $('#twp-transfer-agent-number').val();
if (agentNumber) {
transferCall(agentNumber);
}
});
$(document).on('click', '#twp-cancel-transfer', function() {
hideTransferDialog();
});
// Requeue dialog handlers
$(document).on('click', '#twp-confirm-requeue', function() {
const queueId = $('#twp-requeue-select').val();
if (queueId) {
requeueCall(queueId);
}
});
$(document).on('click', '#twp-cancel-requeue', function() {
hideRequeueDialog();
});
// Accept queue call button
$('#twp-accept-queue-call').on('click', function() {
acceptQueueCall();
stopAlert(); // Stop alert when accepting call
});
// Refresh queues button
$('#twp-refresh-queues').on('click', function() {
loadUserQueues();
});
// Alert toggle button
$(document).on('click', '#twp-alert-toggle', function() {
toggleAlert();
});
// Voicemail refresh button
$('#twp-refresh-voicemails').on('click', function() {
loadUserVoicemails();
});
// View all voicemails button
$('#twp-view-all-voicemails').on('click', function() {
// Open admin voicemails page in new tab
window.open(twp_frontend_ajax.admin_url + 'admin.php?page=twilio-wp-voicemails', '_blank');
});
// Voicemail toggle button
$('#twp-voicemail-toggle').on('click', function() {
toggleVoicemailSection();
});
// Voicemail header click (also toggles)
$('#twp-voicemail-header').on('click', function(e) {
// Don't toggle if clicking the toggle button itself
if (!$(e.target).closest('.voicemail-toggle').length) {
toggleVoicemailSection();
}
});
// Voicemail item click handler
$(document).on('click', '.voicemail-item', function() {
const voicemailId = $(this).data('voicemail-id');
playVoicemail(voicemailId);
});
// Queue item selection
$(document).on('click', '.queue-item', function() {
const queueId = $(this).data('queue-id');
selectQueue(queueId);
});
// Manual number input
$('#twp-dial-number').on('input', function() {
// Only allow valid phone number characters
let value = $(this).val().replace(/[^\d\+\-\(\)\s]/g, '');
$(this).val(value);
});
// Handle enter key in dial number input
$('#twp-dial-number').on('keypress', function(e) {
if (e.which === 13) { // Enter key
$('#twp-call-btn').click();
}
});
}
/**
* Add digit to dial pad
*/
function addDigit(digit) {
const $input = $('#twp-dial-number');
$input.val($input.val() + digit);
// Send DTMF if in a call
if (currentCall && currentCall.status() === 'open') {
currentCall.sendDigits(digit);
}
}
/**
* Make outbound call
*/
async function makeCall(number, callerId) {
if (currentCall) {
showMessage('Already in a call', 'error');
return;
}
// Stop alerts when making a call
stopAlert();
updateCallState('connecting');
showCallInfo('Connecting...');
const params = {
To: number,
From: callerId
};
try {
console.log('Making call with params:', params);
currentCall = await twilioDevice.connect({params: params});
// Setup call event handlers
currentCall.on('accept', function() {
updateCallState('connected');
showCallInfo('Connected');
startCallTimer();
showMessage('Call connected!', 'success');
});
currentCall.on('disconnect', function() {
endCall();
showMessage('Call ended', 'info');
});
currentCall.on('error', function(error) {
console.error('Call error:', error);
endCall();
showMessage('Call failed: ' + error.message, 'error');
});
} catch (error) {
console.error('Failed to make call:', error);
endCall();
showMessage('Failed to make call: ' + error.message, 'error');
}
}
/**
* Handle incoming call
*/
function handleIncomingCall(call) {
currentCall = call;
// Add visual indication
$('.twp-browser-phone-container').addClass('incoming-call');
updateCallState('ringing');
showCallInfo('Incoming call from: ' + (call.parameters.From || 'Unknown'));
showMessage('Incoming call! Click Accept to answer.', 'info');
// Auto-answer after a delay (optional)
setTimeout(function() {
if (currentCall === call && call.status() === 'pending') {
acceptCall();
}
}, 2000);
call.on('accept', function() {
$('.twp-browser-phone-container').removeClass('incoming-call');
updateCallState('connected');
showCallInfo('Connected');
startCallTimer();
showMessage('Call answered!', 'success');
});
call.on('disconnect', function() {
$('.twp-browser-phone-container').removeClass('incoming-call');
endCall();
showMessage('Call ended', 'info');
});
call.on('error', function(error) {
$('.twp-browser-phone-container').removeClass('incoming-call');
endCall();
showMessage('Call error: ' + error.message, 'error');
});
}
/**
* Accept incoming call
*/
function acceptCall() {
if (currentCall && currentCall.status() === 'pending') {
currentCall.accept();
}
}
/**
* Hang up current call
*/
function hangupCall() {
if (currentCall) {
currentCall.disconnect();
}
endCall();
}
/**
* End call and cleanup
*/
function endCall() {
// Stop recording if active
if (isRecording) {
stopRecording();
}
currentCall = null;
isOnHold = false;
isRecording = false;
recordingSid = null;
stopCallTimer();
updateCallState('idle');
hideCallInfo();
$('.twp-browser-phone-container').removeClass('incoming-call');
// Reset control buttons
$('#twp-hold-btn').text('Hold').removeClass('btn-active');
$('#twp-record-btn').text('Record').removeClass('btn-active');
// 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(silent = false) {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_agent_queues',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
// Check for new calls in queues
checkForNewCalls(response.data);
userQueues = response.data;
displayQueues();
} else if (!silent) {
showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
if (!silent) {
showMessage('Failed to load queues', 'error');
}
}
});
}
/**
* Check for new calls in queues and trigger alerts
*/
function checkForNewCalls(newQueues) {
let hasWaitingCalls = false;
let newCallDetected = false;
newQueues.forEach(function(queue) {
const queueId = queue.id;
const currentWaiting = parseInt(queue.current_waiting) || 0;
const previousWaiting = lastQueueUpdate[queueId] || 0;
// Track if any queue has waiting calls
if (currentWaiting > 0) {
hasWaitingCalls = true;
}
// If waiting count increased, we have new calls
if (currentWaiting > previousWaiting) {
console.log('New call detected in queue:', queue.queue_name);
newCallDetected = true;
// Show browser notification for new call
if (notificationPermission === 'granted') {
showBrowserNotification('📞 New Call in Queue!', {
body: `${queue.queue_name}: ${currentWaiting} call${currentWaiting > 1 ? 's' : ''} waiting`,
icon: '📞',
vibrate: [300, 200, 300],
requireInteraction: true,
tag: `queue-${queue.id}`,
data: {
queueId: queue.id
}
});
}
}
lastQueueUpdate[queueId] = currentWaiting;
});
// Manage alerts based on queue state
if (alertEnabled && !currentCall) {
if (newCallDetected) {
// Start alert for new calls
startAlert();
} else if (!hasWaitingCalls) {
// Stop alert if no calls are waiting in any queue
console.log('No calls waiting in any queue, stopping alerts');
stopAlert();
}
}
}
/**
* Display queues in the UI
*/
function displayQueues() {
const $queueList = $('#twp-queue-list');
if (userQueues.length === 0) {
$queueList.html('<div class="no-queues">No queues assigned to you.</div>');
$('#twp-queue-section').hide();
$('#twp-queue-global-actions').hide();
return;
}
$('#twp-queue-section').show();
$('#twp-queue-global-actions').show();
let html = '';
userQueues.forEach(function(queue) {
const hasWaiting = parseInt(queue.current_waiting) > 0;
const waitingCount = queue.current_waiting || 0;
html += `
<div class="queue-item ${hasWaiting ? 'has-calls' : ''}" data-queue-id="${queue.id}">
<div class="queue-name">${queue.queue_name}</div>
<div class="queue-info">
<span class="queue-waiting ${hasWaiting ? 'has-calls' : ''}">
${waitingCount} waiting
</span>
<span class="queue-capacity">
Max: ${queue.max_size}
</span>
</div>
</div>
`;
});
$queueList.html(html);
// Auto-select first queue with calls, or first queue if none have calls
const firstQueueWithCalls = userQueues.find(q => parseInt(q.current_waiting) > 0);
const queueToSelect = firstQueueWithCalls || userQueues[0];
if (queueToSelect) {
selectQueue(queueToSelect.id);
}
}
/**
* Select a queue
*/
function selectQueue(queueId) {
selectedQueue = userQueues.find(q => q.id == queueId);
if (!selectedQueue) return;
// Update UI selection
$('.queue-item').removeClass('selected');
$(`.queue-item[data-queue-id="${queueId}"]`).addClass('selected');
// Update queue controls
$('#selected-queue-name').text(selectedQueue.queue_name);
$('#twp-waiting-count').text(selectedQueue.current_waiting || 0);
$('#twp-queue-max-size').text(selectedQueue.max_size);
// Show queue controls if there are waiting calls
if (parseInt(selectedQueue.current_waiting) > 0) {
$('#twp-queue-controls').show();
} else {
$('#twp-queue-controls').hide();
}
}
/**
* Accept next call from selected queue
*/
function acceptQueueCall() {
if (!selectedQueue) {
showMessage('Please select a queue first', 'error');
return;
}
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_accept_next_queue_call',
queue_id: selectedQueue.id,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
showMessage('Connecting to next caller...', 'info');
// Refresh queue data after accepting call
setTimeout(loadUserQueues, 1000);
} else {
showMessage(response.data || 'No calls waiting in this queue', 'info');
}
},
error: function() {
showMessage('Failed to accept queue call', 'error');
}
});
}
/**
* Update call state UI
*/
function updateCallState(state) {
const $callBtn = $('#twp-call-btn');
const $hangupBtn = $('#twp-hangup-btn');
const $controlsPanel = $('#twp-call-controls-panel');
switch (state) {
case 'idle':
$callBtn.show().prop('disabled', false);
$hangupBtn.hide();
$controlsPanel.hide();
break;
case 'connecting':
case 'ringing':
$callBtn.hide();
$hangupBtn.show();
$controlsPanel.hide();
break;
case 'connected':
$callBtn.hide();
$hangupBtn.show();
$controlsPanel.show();
break;
}
}
/**
* Show call info panel
*/
function showCallInfo(status) {
$('#twp-call-info').show();
$('#twp-call-status').text(status);
}
/**
* Hide call info panel
*/
function hideCallInfo() {
$('#twp-call-info').hide();
$('#twp-call-timer').text('00:00');
$('#twp-call-status').text('');
}
/**
* Start call timer
*/
function startCallTimer() {
callStartTime = new Date();
callTimer = setInterval(updateCallTimer, 1000);
updateCallTimer();
}
/**
* Stop call timer
*/
function stopCallTimer() {
if (callTimer) {
clearInterval(callTimer);
callTimer = null;
}
callStartTime = null;
}
/**
* Update call timer display
*/
function updateCallTimer() {
if (!callStartTime) return;
const elapsed = Math.floor((new Date() - callStartTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const seconds = elapsed % 60;
const timeString = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
$('#twp-call-timer').text(timeString);
}
/**
* Update connection status
*/
function updateStatus(status, text) {
const $indicator = $('#twp-status-indicator');
const $text = $('#twp-status-text');
$indicator.removeClass('offline connecting online').addClass(status);
$text.text(text);
}
/**
* Show message to user
*/
function showMessage(message, type) {
const $messages = $('#twp-messages');
const $message = $('<div>').addClass('twp-' + type).text(message);
$messages.empty().append($message);
// Auto-hide success and info messages
if (type === 'success' || type === 'info') {
setTimeout(function() {
$message.fadeOut(function() {
$message.remove();
});
}, 5000);
}
}
// Start queue polling with faster interval
startQueuePolling();
/**
* Start polling for queue updates
*/
function startQueuePolling() {
// Clear any existing timer
if (queuePollingTimer) {
clearInterval(queuePollingTimer);
}
// 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 10 minutes before expiry for safety
*/
function scheduleTokenRefresh() {
// Clear any existing timer
if (tokenRefreshTimer) {
clearTimeout(tokenRefreshTimer);
}
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 (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) {
// Token needs refresh immediately
refreshToken();
} else {
// Schedule refresh
console.log('Scheduling token refresh in', Math.round(timeUntilRefresh / 1000), 'seconds');
tokenRefreshTimer = setTimeout(refreshToken, timeUntilRefresh);
}
}
/**
* Refresh the capability token
*/
function refreshToken() {
console.log('Refreshing capability token...');
// Don't refresh if currently in a call
if (currentCall) {
console.log('Currently in call, postponing token refresh');
// Retry in 1 minute
setTimeout(refreshToken, 60000);
return;
}
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_generate_capability_token',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
console.log('Token refreshed successfully');
// Update token expiry
tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
// Update device with new token
setupTwilioDevice(response.data.token);
// Schedule next refresh
scheduleTokenRefresh();
} else {
console.error('Failed to refresh token:', response.data);
updateStatus('offline', 'Token refresh failed');
showMessage('Failed to refresh connection. Please refresh the page.', 'error');
}
},
error: function() {
console.error('Failed to refresh token - network error');
updateStatus('offline', 'Connection lost');
// Retry in 30 seconds
setTimeout(refreshToken, 30000);
}
});
}
/**
* 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;
// Check if there are actually waiting calls before starting alert
const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0);
if (!hasWaitingCalls) {
console.log('No waiting calls found, not starting alert');
return;
}
// Play initial alert
playAlertSound();
// Repeat every 30 seconds
alertInterval = setInterval(function() {
if (alertEnabled && !currentCall) {
// Check if there are still waiting calls
const stillHasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0);
if (stillHasWaitingCalls) {
playAlertSound();
} else {
console.log('No more waiting calls, stopping alert');
stopAlert();
}
} 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 (backgroundAlertInterval) {
clearInterval(backgroundAlertInterval);
}
if (personalQueueTimer) {
clearInterval(personalQueueTimer);
}
if (twilioDevice) {
twilioDevice.destroy();
}
});
/**
* Load user's voicemails
*/
function loadUserVoicemails(silent = false) {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_user_voicemails',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
displayVoicemails(response.data);
} else if (!silent) {
showMessage('Failed to load voicemails: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
if (!silent) {
showMessage('Failed to load voicemails', 'error');
}
}
});
}
/**
* Toggle voicemail section visibility
*/
function toggleVoicemailSection() {
const $content = $('#twp-voicemail-content');
const $toggle = $('#twp-voicemail-toggle .toggle-icon');
const isVisible = $content.is(':visible');
if (isVisible) {
$content.slideUp(300);
$toggle.text('▼');
localStorage.setItem('twp_voicemail_collapsed', 'true');
} else {
$content.slideDown(300);
$toggle.text('▲');
localStorage.setItem('twp_voicemail_collapsed', 'false');
// Load voicemails when expanding if not already loaded
if ($('#twp-voicemail-list').children('.voicemail-loading').length > 0) {
loadUserVoicemails();
}
}
}
/**
* Initialize voicemail section state
*/
function initVoicemailSection() {
const isCollapsed = localStorage.getItem('twp_voicemail_collapsed') === 'true';
const $content = $('#twp-voicemail-content');
const $toggle = $('#twp-voicemail-toggle .toggle-icon');
if (isCollapsed) {
$content.hide();
$toggle.text('▼');
} else {
$content.show();
$toggle.text('▲');
// Load voicemails immediately if expanded
loadUserVoicemails();
}
}
/**
* Display voicemails in the UI
*/
function displayVoicemails(data) {
const $voicemailList = $('#twp-voicemail-list');
// Update stats
$('#twp-total-voicemails').text(data.total_count || 0);
$('#twp-today-voicemails').text(data.today_count || 0);
if (!data.voicemails || data.voicemails.length === 0) {
$voicemailList.html('<div class="no-voicemails">No voicemails found.</div>');
return;
}
let html = '';
data.voicemails.forEach(function(voicemail) {
const hasTranscription = voicemail.transcription && voicemail.transcription !== 'No transcription';
const hasRecording = voicemail.has_recording;
html += `
<div class="voicemail-item ${hasRecording ? 'has-recording' : ''}" data-voicemail-id="${voicemail.id}">
<div class="voicemail-header">
<div class="voicemail-from">
<span class="phone-icon">📞</span>
<span class="from-number">${voicemail.from_number}</span>
</div>
<div class="voicemail-time">${voicemail.time_ago}</div>
</div>
<div class="voicemail-details">
<div class="voicemail-duration">
<span class="duration-icon">⏱️</span>
<span>${formatDuration(voicemail.duration)}</span>
</div>
${hasRecording ? '<span class="recording-indicator">🎵 Recording</span>' : ''}
</div>
${hasTranscription ? `<div class="voicemail-transcription">${voicemail.transcription}</div>` : ''}
</div>
`;
});
$voicemailList.html(html);
}
/**
* Format duration in seconds to mm:ss
*/
function formatDuration(seconds) {
if (!seconds || seconds === 0) return '0:00';
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return minutes + ':' + String(remainingSeconds).padStart(2, '0');
}
/**
* Play voicemail audio
*/
function playVoicemail(voicemailId) {
if (!voicemailId) return;
// Get voicemail audio URL and play it
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_voicemail_audio',
voicemail_id: voicemailId,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success && response.data.audio_url) {
// Create and play audio element
const audio = new Audio(response.data.audio_url);
audio.play().catch(function(error) {
showMessage('Failed to play voicemail: ' + error.message, 'error');
});
showMessage('Playing voicemail...', 'info');
} else {
showMessage('No audio available for this voicemail', 'error');
}
},
error: function() {
showMessage('Failed to load voicemail audio', 'error');
}
});
}
/**
* Initialize browser notifications
*/
function initializeNotifications() {
// Register service worker for background notifications
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/wp-content/plugins/twilio-wp-plugin/assets/js/twp-service-worker.js')
.then(function(registration) {
console.log('Service Worker registered:', registration);
})
.catch(function(error) {
console.log('Service Worker registration failed:', error);
});
}
// Check if browser supports notifications
if (!('Notification' in window)) {
console.log('This browser does not support notifications');
return;
}
// Check current permission status
notificationPermission = Notification.permission;
// Request permission if not already granted or denied
if (notificationPermission === 'default') {
// Add a button to request permission
if ($('#twp-queue-global-actions').length > 0) {
const $notificationBtn = $('<button>')
.attr('id', 'twp-enable-notifications')
.addClass('twp-btn twp-btn-info')
.html('🔔 Enable Alerts')
.on('click', requestNotificationPermission);
$('#twp-queue-global-actions .global-queue-actions').append($notificationBtn);
}
} else if (notificationPermission === 'granted') {
console.log('Notifications are enabled');
}
}
/**
* Request notification permission from user
*/
function requestNotificationPermission() {
Notification.requestPermission().then(function(permission) {
notificationPermission = permission;
if (permission === 'granted') {
showMessage('Notifications enabled! You will be alerted even when the browser is in the background.', 'success');
$('#twp-enable-notifications').hide();
// Show test notification
showBrowserNotification('Notifications Enabled', {
body: 'You will now receive alerts for incoming calls',
icon: '📞',
tag: 'test-notification'
});
} else if (permission === 'denied') {
showMessage('Notifications blocked. Please enable them in your browser settings.', 'error');
$('#twp-enable-notifications').text('❌ Notifications Blocked');
}
});
}
/**
* Show browser notification
*/
function showBrowserNotification(title, options = {}) {
if (notificationPermission !== 'granted') {
return;
}
const defaultOptions = {
body: '',
icon: '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-icon.png',
badge: '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-badge.png',
vibrate: [200, 100, 200],
requireInteraction: true, // Keep notification visible until clicked
tag: 'twp-call-notification',
data: options.data || {}
};
const notificationOptions = Object.assign(defaultOptions, options);
try {
const notification = new Notification(title, notificationOptions);
// Handle notification click
notification.onclick = function(event) {
event.preventDefault();
window.focus();
notification.close();
// If there's queue data, select that queue
if (event.target.data && event.target.data.queueId) {
selectQueue(event.target.data.queueId);
}
};
// Auto-close after 30 seconds if not required interaction
if (!notificationOptions.requireInteraction) {
setTimeout(function() {
notification.close();
}, 30000);
}
return notification;
} catch (error) {
console.error('Failed to show notification:', error);
// Fallback to service worker notification if available
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
navigator.serviceWorker.ready.then(function(registration) {
registration.showNotification(title, notificationOptions);
});
}
}
}
/**
* Initialize page visibility handling
*/
function initializePageVisibility() {
// Set up visibility change detection
let hidden, visibilityChange;
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
// Handle visibility change
document.addEventListener(visibilityChange, function() {
isPageVisible = !document[hidden];
if (isPageVisible) {
console.log('Page is now visible');
// Resume normal operations
if (backgroundAlertInterval) {
clearInterval(backgroundAlertInterval);
backgroundAlertInterval = null;
}
} else {
console.log('Page is now hidden/background');
// Start more aggressive notifications for background
if (alertEnabled && userQueues.some(q => parseInt(q.current_waiting) > 0)) {
startBackgroundAlerts();
}
}
}, false);
// Also handle window focus/blur for better mobile support
$(window).on('focus', function() {
isPageVisible = true;
if (backgroundAlertInterval) {
clearInterval(backgroundAlertInterval);
backgroundAlertInterval = null;
}
});
$(window).on('blur', function() {
isPageVisible = false;
});
}
/**
* Start background alerts with notifications
*/
function startBackgroundAlerts() {
if (backgroundAlertInterval) return;
// Check and notify every 10 seconds when in background
backgroundAlertInterval = setInterval(function() {
const waitingQueues = userQueues.filter(q => parseInt(q.current_waiting) > 0);
if (waitingQueues.length > 0 && !currentCall) {
// Count total waiting calls
const totalWaiting = waitingQueues.reduce((sum, q) => sum + parseInt(q.current_waiting), 0);
// Show browser notification
showBrowserNotification(`${totalWaiting} Call${totalWaiting > 1 ? 's' : ''} Waiting!`, {
body: waitingQueues.map(q => `${q.queue_name}: ${q.current_waiting} waiting`).join('\n'),
icon: '📞',
vibrate: [300, 200, 300, 200, 300],
requireInteraction: true,
tag: 'queue-alert',
data: {
queueId: waitingQueues[0].id
}
});
// Also try to play sound if possible
try {
playAlertSound();
} catch (e) {
// Sound might be blocked in background
}
} else if (waitingQueues.length === 0) {
// No more calls, stop background alerts
clearInterval(backgroundAlertInterval);
backgroundAlertInterval = null;
}
}, 10000); // Every 10 seconds in background
}
// Load alert preference on init
loadAlertPreference();
/**
* Initialize personal queue for incoming transfers
*/
function initializePersonalQueue() {
if (!twp_frontend_ajax.user_id) return;
// Set personal queue name
personalQueueName = 'agent_' + twp_frontend_ajax.user_id;
// Start polling for incoming transfers
checkPersonalQueue();
personalQueueTimer = setInterval(checkPersonalQueue, 3000); // Check every 3 seconds
}
/**
* Check personal queue for incoming transfers
*/
function checkPersonalQueue() {
// Don't check if already in a call
if (currentCall) return;
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_check_personal_queue',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success && response.data.has_waiting_call) {
handleIncomingTransfer(response.data);
}
},
error: function() {
// Silently fail - don't interrupt user
}
});
}
/**
* Handle incoming transfer notification
*/
function handleIncomingTransfer(data) {
// Show notification
showMessage('Incoming transfer! The call will be connected automatically.', 'info');
// Show browser notification
if (notificationPermission === 'granted') {
showBrowserNotification('📞 Incoming Transfer!', {
body: 'A call is being transferred to you',
icon: '📞',
vibrate: [300, 200, 300],
requireInteraction: true,
tag: 'transfer-notification'
});
}
// Play alert sound if enabled
if (alertEnabled) {
playAlertSound();
}
// Auto-accept the transfer after a short delay
setTimeout(function() {
acceptTransferCall(data);
}, 2000);
}
/**
* Accept incoming transfer call
*/
function acceptTransferCall(data) {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_accept_transfer_call',
call_sid: data.call_sid,
queue_id: data.queue_id,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
showMessage('Transfer accepted, connecting...', 'success');
} else {
showMessage('Failed to accept transfer: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to accept transfer', 'error');
}
});
}
/**
* Toggle call hold
*/
function toggleHold() {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to hold', 'error');
return;
}
const $holdBtn = $('#twp-hold-btn');
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_toggle_hold',
call_sid: currentCall.parameters.CallSid,
hold: !isOnHold,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
isOnHold = !isOnHold;
if (isOnHold) {
$holdBtn.text('Unhold').addClass('btn-active');
showMessage('Call placed on hold', 'info');
} else {
$holdBtn.text('Hold').removeClass('btn-active');
showMessage('Call resumed', 'info');
}
} else {
showMessage('Failed to toggle hold: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to toggle hold', 'error');
}
});
}
/**
* Transfer call to another agent
*/
function transferCall(agentNumber) {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to transfer', 'error');
return;
}
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_transfer_call',
call_sid: currentCall.parameters.CallSid,
agent_number: agentNumber,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
showMessage('Call transferred successfully', 'success');
hideTransferDialog();
// End the call on our end
if (currentCall) {
currentCall.disconnect();
}
} else {
showMessage('Failed to transfer call: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to transfer call', 'error');
}
});
}
/**
* Requeue call to a different queue
*/
function requeueCall(queueId) {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to requeue', 'error');
return;
}
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_requeue_call',
call_sid: currentCall.parameters.CallSid,
queue_id: queueId,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
showMessage('Call requeued successfully', 'success');
hideRequeueDialog();
// End the call on our end
if (currentCall) {
currentCall.disconnect();
}
} else {
showMessage('Failed to requeue call: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to requeue call', 'error');
}
});
}
/**
* Toggle call recording
*/
function toggleRecording() {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to record', 'error');
return;
}
if (isRecording) {
stopRecording();
} else {
startRecording();
}
}
/**
* Start recording the current call
*/
function startRecording() {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to record', 'error');
return;
}
// Try multiple ways to get the call SID
const callSid = currentCall.parameters.CallSid ||
currentCall.customParameters.CallSid ||
currentCall.outgoingConnectionId ||
currentCall.sid;
console.log('Frontend currentCall object:', currentCall);
console.log('Frontend attempting to record call SID:', callSid);
if (!callSid) {
showMessage('Could not determine call SID for recording', 'error');
return;
}
const $recordBtn = $('#twp-record-btn');
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_start_recording',
call_sid: callSid,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
isRecording = true;
recordingSid = response.data.recording_sid;
$recordBtn.text('Stop Recording').addClass('btn-active btn-recording');
showMessage('Recording started', 'success');
} else {
showMessage('Failed to start recording: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function(xhr, status, error) {
console.error('Frontend recording start failed:', xhr.responseText);
showMessage('Failed to start recording: ' + error, 'error');
}
});
}
/**
* Stop recording the current call
*/
function stopRecording() {
if (!recordingSid) return;
const $recordBtn = $('#twp-record-btn');
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_stop_recording',
call_sid: currentCall ? currentCall.parameters.CallSid : '',
recording_sid: recordingSid,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
isRecording = false;
recordingSid = null;
$recordBtn.text('Record').removeClass('btn-active btn-recording');
showMessage('Recording stopped', 'info');
} else {
showMessage('Failed to stop recording: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to stop recording', 'error');
}
});
}
/**
* Show transfer dialog
*/
function showTransferDialog() {
// First load available agents
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_online_agents',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success && response.data.length > 0) {
showAgentTransferDialog(response.data);
} else {
// Fallback to manual phone number entry
showManualTransferDialog();
}
},
error: function() {
// Fallback to manual phone number entry
showManualTransferDialog();
}
});
}
/**
* Show agent selection transfer dialog
*/
function showAgentTransferDialog(agents) {
let agentOptions = '<div class="agent-list">';
agents.forEach(function(agent) {
const statusClass = agent.is_available ? 'available' : (agent.status === 'busy' ? 'busy' : 'offline');
const statusText = agent.is_available ? '🟢 Available' : (agent.status === 'busy' ? '🔴 Busy' : '⚫ Offline');
const methodIcon = agent.has_phone ? '📱' : '💻';
agentOptions += `
<div class="agent-option ${statusClass}" data-agent-id="${agent.id}"
data-transfer-method="${agent.transfer_method}"
data-transfer-value="${agent.transfer_value}">
<div class="agent-info">
<span class="agent-name">${agent.name}</span>
<span class="agent-method">${methodIcon}</span>
</div>
<div class="agent-status">${statusText}</div>
</div>
`;
});
agentOptions += '</div>';
const dialog = `
<div id="twp-transfer-dialog" class="twp-dialog-overlay">
<div class="twp-dialog twp-agent-transfer-dialog">
<h3>Transfer Call to Agent</h3>
<p>Select an agent to transfer this call to:</p>
${agentOptions}
<div class="manual-option">
<p>Or enter a phone number manually:</p>
<input type="tel" id="twp-transfer-manual-number" placeholder="+1234567890" />
</div>
<div class="dialog-buttons">
<button id="twp-confirm-agent-transfer" class="twp-btn twp-btn-primary" disabled>Transfer</button>
<button id="twp-cancel-transfer" class="twp-btn twp-btn-secondary">Cancel</button>
</div>
</div>
</div>
`;
$('body').append(dialog);
// Handle agent selection
let selectedAgent = null;
$('.agent-option').on('click', function() {
if ($(this).hasClass('offline')) {
showMessage('Cannot transfer to offline agents', 'error');
return;
}
$('.agent-option').removeClass('selected');
$(this).addClass('selected');
selectedAgent = {
id: $(this).data('agent-id'),
method: $(this).data('transfer-method'),
value: $(this).data('transfer-value')
};
$('#twp-transfer-manual-number').val('');
$('#twp-confirm-agent-transfer').prop('disabled', false);
});
// Handle manual number entry
$('#twp-transfer-manual-number').on('input', function() {
const number = $(this).val().trim();
if (number) {
$('.agent-option').removeClass('selected');
selectedAgent = null;
$('#twp-confirm-agent-transfer').prop('disabled', false);
} else {
$('#twp-confirm-agent-transfer').prop('disabled', !selectedAgent);
}
});
// Handle transfer confirmation
$('#twp-confirm-agent-transfer').on('click', function() {
const manualNumber = $('#twp-transfer-manual-number').val().trim();
if (manualNumber) {
// Manual phone transfer
transferCall(manualNumber);
} else if (selectedAgent) {
// Agent transfer (phone or queue)
transferToAgent(selectedAgent);
}
});
}
/**
* Show manual transfer dialog (fallback)
*/
function showManualTransferDialog() {
const dialog = `
<div id="twp-transfer-dialog" class="twp-dialog-overlay">
<div class="twp-dialog">
<h3>Transfer Call</h3>
<p>Enter the phone number to transfer this call:</p>
<input type="tel" id="twp-transfer-agent-number" placeholder="+1234567890" />
<div class="dialog-buttons">
<button id="twp-confirm-transfer" class="twp-btn twp-btn-primary">Transfer</button>
<button id="twp-cancel-transfer" class="twp-btn twp-btn-secondary">Cancel</button>
</div>
</div>
</div>
`;
$('body').append(dialog);
}
/**
* Transfer call to selected agent
*/
function transferToAgent(agent) {
if (!currentCall || currentCall.status() !== 'open') {
showMessage('No active call to transfer', 'error');
return;
}
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_transfer_to_agent_queue',
call_sid: currentCall.parameters.CallSid,
agent_id: agent.id,
transfer_method: agent.method,
transfer_value: agent.value,
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
showMessage('Call transferred successfully', 'success');
hideTransferDialog();
// End the call on our end
if (currentCall) {
currentCall.disconnect();
}
} else {
showMessage('Failed to transfer call: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to transfer call', 'error');
}
});
}
/**
* Hide transfer dialog
*/
function hideTransferDialog() {
$('#twp-transfer-dialog').remove();
}
/**
* Show requeue dialog
*/
function showRequeueDialog() {
// Load available queues first
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_all_queues',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success && response.data.length > 0) {
let options = '';
response.data.forEach(function(queue) {
options += `<option value="${queue.id}">${queue.queue_name}</option>`;
});
const dialog = `
<div id="twp-requeue-dialog" class="twp-dialog-overlay">
<div class="twp-dialog">
<h3>Requeue Call</h3>
<p>Select a queue to transfer this call to:</p>
<select id="twp-requeue-select">
${options}
</select>
<div class="dialog-buttons">
<button id="twp-confirm-requeue" class="twp-btn twp-btn-primary">Requeue</button>
<button id="twp-cancel-requeue" class="twp-btn twp-btn-secondary">Cancel</button>
</div>
</div>
</div>
`;
$('body').append(dialog);
} else {
showMessage('No queues available', 'error');
}
},
error: function() {
showMessage('Failed to load queues', 'error');
}
});
}
/**
* Hide requeue dialog
*/
function hideRequeueDialog() {
$('#twp-requeue-dialog').remove();
}
})(jQuery);