/** * 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; // 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(); }); /** * Initialize the browser phone */ function initializeBrowserPhone() { updateStatus('connecting', 'Initializing...'); // 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); } 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 */ function setupTwilioDevice(token) { try { twilioDevice = new Twilio.Device(token, { logLevel: 1, answerOnBridge: true }); twilioDevice.on('registered', function() { updateStatus('online', 'Ready'); isConnected = true; 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'); }); twilioDevice.on('incoming', function(call) { handleIncomingCall(call); }); twilioDevice.on('disconnect', function() { updateStatus('offline', 'Disconnected'); isConnected = false; }); 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(''); return; } $select.append(''); availableNumbers.forEach(function(number) { const friendlyName = number.friendly_name || number.phone_number; $select.append(``); }); } /** * 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(); }); // Accept queue call button $('#twp-accept-queue-call').on('click', function() { acceptQueueCall(); }); // Refresh queues button $('#twp-refresh-queues').on('click', function() { loadUserQueues(); }); // 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 */ function makeCall(number, callerId) { if (currentCall) { showMessage('Already in a call', 'error'); return; } updateCallState('connecting'); showCallInfo('Connecting...'); const params = { To: number, From: callerId }; try { currentCall = twilioDevice.connect(params); 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() { currentCall = null; stopCallTimer(); updateCallState('idle'); hideCallInfo(); $('.twp-browser-phone-container').removeClass('incoming-call'); } /** * Load user's assigned queues */ function loadUserQueues() { $.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) { userQueues = response.data; displayQueues(); } else { showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error'); } }, error: function() { showMessage('Failed to load queues', 'error'); } }); } /** * Display queues in the UI */ function displayQueues() { const $queueList = $('#twp-queue-list'); if (userQueues.length === 0) { $queueList.html('
No queues assigned to you.
'); $('#twp-queue-section').hide(); return; } $('#twp-queue-section').show(); let html = ''; userQueues.forEach(function(queue) { const hasWaiting = parseInt(queue.current_waiting) > 0; const waitingCount = queue.current_waiting || 0; html += `
${queue.queue_name}
${waitingCount} waiting Max: ${queue.max_size}
`; }); $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'); switch (state) { case 'idle': $callBtn.show().prop('disabled', false); $hangupBtn.hide(); break; case 'connecting': case 'ringing': $callBtn.hide(); $hangupBtn.show(); break; case 'connected': $callBtn.hide(); $hangupBtn.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 = $('
').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); } } // Periodic status updates setInterval(function() { if (isConnected) { loadUserQueues(); // This will refresh all queue data including waiting counts } }, 30000); // Every 30 seconds })(jQuery);