From 4c353096fe97db1d2a8925ef90ca42bcc0dbcef0 Mon Sep 17 00:00:00 2001 From: jknapp Date: Wed, 13 Aug 2025 13:45:25 -0700 Subject: [PATCH] Add mobile-friendly browser phone shortcode for frontend use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created TWP_Shortcodes class with [twp_browser_phone] shortcode - Mobile-first responsive CSS design with touch-friendly interface - Frontend JavaScript integration with Twilio Voice SDK v2 - Permission checks for logged-in users with twp_access_browser_phone capability - Haptic feedback and dark mode support - Configurable shortcode parameters (title, show_title, compact) - Updated plugin version to 2.1.0 in main file - Added comprehensive documentation to README.md Features: - Visual dialpad with DTMF support - Real-time connection status indicators - Call timer and queue management - Auto-scaling for mobile devices (320px+) - Optimized for both desktop and mobile use 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 36 +- assets/css/browser-phone-frontend.css | 462 ++++++++++++++++++++++++ assets/js/browser-phone-frontend.js | 500 ++++++++++++++++++++++++++ includes/class-twp-core.php | 7 + includes/class-twp-shortcodes.php | 178 +++++++++ twilio-wp-plugin.php | 2 +- 6 files changed, 1183 insertions(+), 2 deletions(-) create mode 100644 assets/css/browser-phone-frontend.css create mode 100644 assets/js/browser-phone-frontend.js create mode 100644 includes/class-twp-shortcodes.php diff --git a/README.md b/README.md index c8059a6..0054712 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,15 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will - **Queue Notifications**: SMS alerts to designated numbers when calls enter queues ### 🌐 Browser Phone (WebRTC) -- **In-Browser Calling**: Make and receive calls directly from WordPress admin +- **In-Browser Calling**: Make and receive calls directly from WordPress admin or frontend - **Twilio Voice SDK v2**: Uses latest SDK for WebRTC functionality - **Visual Dialpad**: Click-to-dial interface with DTMF support - **Call Controls**: Mute, hold indicators, call timer - **Auto-Answer**: Optional automatic call acceptance - **Queue Integration**: Accept calls from specific queues - **Token Management**: Automatic token refresh for uninterrupted service +- **Mobile-Friendly**: Responsive design optimized for smartphones and tablets +- **Shortcode Support**: Embed browser phone on any page with `[twp_browser_phone]` ### 🕒 Business Hours Management - **Schedule-based Routing**: Different call flows for business hours vs after-hours @@ -258,6 +260,38 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will - **Queue Timeouts**: Alert when calls wait too long (configurable threshold) - **Missed Calls**: Alert when calls are abandoned or timeout +## Frontend Browser Phone + +### Shortcode Usage +The browser phone can be embedded on any WordPress page using the `[twp_browser_phone]` shortcode. This is perfect for creating dedicated agent pages or customer service portals. + +**Basic Usage:** +``` +[twp_browser_phone] +``` + +**With Options:** +``` +[twp_browser_phone title="Customer Service Phone" show_title="true" compact="false"] +``` + +### Shortcode Parameters +- **title**: Custom title for the phone widget (default: "Browser Phone") +- **show_title**: Display the title above the phone (default: "true") +- **compact**: Use compact layout for smaller spaces (default: "false") + +### Permission Requirements +- Users must be **logged in** to access the browser phone +- Users need the **`twp_access_browser_phone`** capability or **`manage_options`** +- Phone Agent role users have access by default + +### Mobile Optimization +- **Responsive Design**: Adapts to all screen sizes +- **Touch-Friendly**: Large buttons optimized for mobile devices +- **Haptic Feedback**: Vibration on button press (where supported) +- **Auto-Zoom Prevention**: Proper viewport handling for mobile browsers +- **Dark Mode Support**: Automatically adapts to user's system preference + ## Voice Configuration ### ElevenLabs Integration diff --git a/assets/css/browser-phone-frontend.css b/assets/css/browser-phone-frontend.css new file mode 100644 index 0000000..c799b0d --- /dev/null +++ b/assets/css/browser-phone-frontend.css @@ -0,0 +1,462 @@ +/* Twilio Browser Phone Frontend Styles - Mobile First */ +.twp-browser-phone-container { + max-width: 400px; + margin: 0 auto; + padding: 20px; + background: #f8f9fa; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.twp-browser-phone-container.compact { + padding: 15px; + max-width: 320px; +} + +.twp-browser-phone-title { + text-align: center; + margin: 0 0 20px 0; + color: #333; + font-size: 1.5rem; + font-weight: 600; +} + +/* Connection Status */ +.twp-connection-status { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; + padding: 12px; + background: #fff; + border-radius: 8px; + border: 2px solid #e9ecef; +} + +.status-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 8px; + animation: pulse 2s infinite; +} + +.status-indicator.offline { + background-color: #dc3545; +} + +.status-indicator.connecting { + background-color: #ffc107; +} + +.status-indicator.online { + background-color: #28a745; + animation: none; +} + +.status-text { + font-weight: 500; + color: #495057; +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.5; } + 100% { opacity: 1; } +} + +/* Phone Number Selection */ +.twp-phone-selection { + margin-bottom: 20px; +} + +.twp-phone-selection label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #495057; +} + +.twp-select { + width: 100%; + padding: 12px 16px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 16px; + background: #fff; + color: #495057; + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 12px center; + background-repeat: no-repeat; + background-size: 16px; + padding-right: 40px; +} + +.twp-select:focus { + outline: none; + border-color: #007cba; + box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); +} + +/* Dial Pad */ +.twp-dialpad { + margin-bottom: 20px; +} + +.twp-number-display { + position: relative; + margin-bottom: 20px; +} + +#twp-dial-number { + width: 100%; + padding: 16px 50px 16px 16px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 18px; + text-align: center; + background: #fff; + letter-spacing: 2px; +} + +#twp-dial-number:focus { + outline: none; + border-color: #007cba; + box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); +} + +.twp-btn-clear { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background: #dc3545; + color: white; + border: none; + border-radius: 50%; + width: 32px; + height: 32px; + font-size: 18px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.twp-btn-clear:hover { + background: #c82333; +} + +/* Dial Grid */ +.twp-dial-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + max-width: 280px; + margin: 0 auto; +} + +.twp-dial-btn { + background: #fff; + border: 2px solid #e9ecef; + border-radius: 12px; + padding: 16px 8px; + font-size: 20px; + font-weight: 600; + color: #333; + cursor: pointer; + transition: all 0.2s ease; + min-height: 60px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + user-select: none; + -webkit-user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.twp-dial-btn:hover { + background: #f8f9fa; + border-color: #007cba; + transform: translateY(-1px); +} + +.twp-dial-btn:active { + transform: translateY(0); + background: #e9ecef; +} + +.twp-dial-btn span { + font-size: 11px; + color: #6c757d; + margin-top: 2px; + font-weight: 400; +} + +/* Call Controls */ +.twp-call-controls { + display: flex; + gap: 12px; + margin-bottom: 20px; +} + +.twp-btn { + flex: 1; + padding: 16px 24px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 56px; +} + +.twp-btn-primary { + background: #28a745; + color: white; +} + +.twp-btn-primary:hover { + background: #218838; + transform: translateY(-1px); +} + +.twp-btn-primary:disabled { + background: #6c757d; + cursor: not-allowed; + transform: none; +} + +.twp-btn-danger { + background: #dc3545; + color: white; +} + +.twp-btn-danger:hover { + background: #c82333; + transform: translateY(-1px); +} + +.twp-btn-success { + background: #007cba; + color: white; +} + +.twp-btn-success:hover { + background: #005a87; + transform: translateY(-1px); +} + +/* Call Info */ +.twp-call-info { + background: #fff; + padding: 16px; + border-radius: 8px; + border: 2px solid #e9ecef; + margin-bottom: 20px; +} + +.call-timer { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.timer-label { + color: #6c757d; + font-weight: 500; +} + +.timer-value { + font-size: 18px; + font-weight: 600; + color: #333; + font-family: 'Courier New', monospace; +} + +.call-status { + text-align: center; + padding: 8px; + background: #f8f9fa; + border-radius: 4px; + color: #495057; + font-weight: 500; +} + +/* Queue Controls */ +.twp-queue-controls { + background: #fff; + padding: 16px; + border-radius: 8px; + border: 2px solid #e9ecef; + margin-bottom: 20px; +} + +.twp-queue-controls h4 { + margin: 0 0 12px 0; + color: #333; + font-size: 1.1rem; +} + +.queue-status { + margin-bottom: 12px; + color: #6c757d; + font-weight: 500; +} + +/* Messages */ +.twp-messages { + margin-top: 16px; +} + +.twp-error { + background: #f8d7da; + color: #721c24; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid #f5c6cb; + margin-bottom: 16px; +} + +.twp-success { + background: #d4edda; + color: #155724; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid #c3e6cb; + margin-bottom: 16px; +} + +.twp-info { + background: #d1ecf1; + color: #0c5460; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid #bee5eb; + margin-bottom: 16px; +} + +/* Responsive Design */ +@media (max-width: 480px) { + .twp-browser-phone-container { + margin: 0; + padding: 16px; + border-radius: 0; + max-width: none; + } + + .twp-browser-phone-title { + font-size: 1.3rem; + margin-bottom: 16px; + } + + .twp-dial-grid { + gap: 8px; + max-width: 240px; + } + + .twp-dial-btn { + padding: 12px 6px; + min-height: 50px; + font-size: 18px; + } + + .twp-dial-btn span { + font-size: 10px; + } + + .twp-btn { + padding: 14px 20px; + font-size: 15px; + min-height: 50px; + } + + #twp-dial-number { + font-size: 16px; + padding: 14px 45px 14px 14px; + } +} + +@media (max-width: 320px) { + .twp-dial-grid { + max-width: 200px; + } + + .twp-dial-btn { + padding: 10px 4px; + min-height: 45px; + font-size: 16px; + } +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .twp-browser-phone-container { + background: #2d3748; + color: #e2e8f0; + } + + .twp-connection-status, + .twp-call-info, + .twp-queue-controls { + background: #4a5568; + border-color: #718096; + } + + .twp-select, + #twp-dial-number, + .twp-dial-btn { + background: #4a5568; + border-color: #718096; + color: #e2e8f0; + } + + .call-status { + background: #2d3748; + } +} + +/* Animation for incoming calls */ +@keyframes incoming-call { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.twp-browser-phone-container.incoming-call { + animation: incoming-call 1s ease-in-out infinite; +} + +/* Loading state */ +.twp-loading { + opacity: 0.6; + pointer-events: none; +} + +.twp-loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + margin: -10px 0 0 -10px; + border: 2px solid #f3f3f3; + border-top: 2px solid #007cba; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/assets/js/browser-phone-frontend.js b/assets/js/browser-phone-frontend.js new file mode 100644 index 0000000..c88c46b --- /dev/null +++ b/assets/js/browser-phone-frontend.js @@ -0,0 +1,500 @@ +/** + * 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 = []; + + // 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(); + }); + + /** + * 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(); + }); + + // 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'); + } + + /** + * Accept next call from queue + */ + function acceptQueueCall() { + $.ajax({ + url: twp_frontend_ajax.ajax_url, + method: 'POST', + data: { + action: 'twp_accept_next_queue_call', + nonce: twp_frontend_ajax.nonce + }, + success: function(response) { + if (response.success) { + showMessage('Connecting to next caller...', 'info'); + } else { + showMessage(response.data || 'No calls waiting', '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) { + loadWaitingCallsCount(); + } + }, 30000); // Every 30 seconds + + /** + * Load waiting calls count for queue display + */ + function loadWaitingCallsCount() { + $.ajax({ + url: twp_frontend_ajax.ajax_url, + method: 'POST', + data: { + action: 'twp_get_waiting_calls', + nonce: twp_frontend_ajax.nonce + }, + success: function(response) { + if (response.success) { + $('#twp-waiting-count').text(response.data.count || 0); + + // Show queue controls if user has permission and there are waiting calls + if (response.data.count > 0) { + $('#twp-queue-controls').show(); + } else { + $('#twp-queue-controls').hide(); + } + } + } + }); + } + +})(jQuery); \ No newline at end of file diff --git a/includes/class-twp-core.php b/includes/class-twp-core.php index 9f6101f..051184e 100644 --- a/includes/class-twp-core.php +++ b/includes/class-twp-core.php @@ -43,6 +43,7 @@ class TWP_Core { require_once TWP_PLUGIN_DIR . 'includes/class-twp-agent-groups.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-agent-manager.php'; require_once TWP_PLUGIN_DIR . 'includes/class-twp-callback-manager.php'; + require_once TWP_PLUGIN_DIR . 'includes/class-twp-shortcodes.php'; // Admin classes require_once TWP_PLUGIN_DIR . 'admin/class-twp-admin.php'; @@ -197,6 +198,9 @@ class TWP_Core { $this->loader->add_action('wp_ajax_twp_delete_conversation', $plugin_admin, 'ajax_delete_conversation'); $this->loader->add_action('wp_ajax_twp_get_conversation', $plugin_admin, 'ajax_get_conversation'); $this->loader->add_action('wp_ajax_twp_send_sms_reply', $plugin_admin, 'ajax_send_sms_reply'); + + // Frontend browser phone AJAX handlers are already covered by the admin handlers above + // since they check permissions internally } /** @@ -210,6 +214,9 @@ class TWP_Core { // Initialize Agent Manager TWP_Agent_Manager::init(); + // Initialize Shortcodes + TWP_Shortcodes::init(); + // Scheduled events $scheduler = new TWP_Scheduler(); $this->loader->add_action('twp_check_schedules', $scheduler, 'check_active_schedules'); diff --git a/includes/class-twp-shortcodes.php b/includes/class-twp-shortcodes.php new file mode 100644 index 0000000..acb065c --- /dev/null +++ b/includes/class-twp-shortcodes.php @@ -0,0 +1,178 @@ +post_content, 'twp_browser_phone')) { + // Enqueue Twilio Voice SDK + wp_enqueue_script( + 'twilio-voice-sdk', + 'https://sdk.twilio.com/js/voice/2.11.1/twilio.min.js', + array(), + '2.11.1', + true + ); + + // Enqueue our browser phone script + wp_enqueue_script( + 'twp-browser-phone-frontend', + TWP_PLUGIN_URL . 'assets/js/browser-phone-frontend.js', + array('jquery', 'twilio-voice-sdk'), + TWP_VERSION, + true + ); + + // Enqueue mobile-friendly styles + wp_enqueue_style( + 'twp-browser-phone-frontend', + TWP_PLUGIN_URL . 'assets/css/browser-phone-frontend.css', + array(), + TWP_VERSION + ); + + // Localize script with AJAX data + wp_localize_script('twp-browser-phone-frontend', 'twp_frontend_ajax', array( + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('twp_frontend_nonce'), + 'user_id' => get_current_user_id(), + 'is_logged_in' => is_user_logged_in() + )); + } + } + + /** + * Browser phone shortcode handler + */ + public static function browser_phone_shortcode($atts) { + // Check if user is logged in + if (!is_user_logged_in()) { + return '
You must be logged in to access the browser phone.
'; + } + + // Check if user has permission + if (!current_user_can('twp_access_browser_phone') && !current_user_can('manage_options')) { + return '
You don\'t have permission to access the browser phone.
'; + } + + // Parse shortcode attributes + $atts = shortcode_atts(array( + 'title' => 'Browser Phone', + 'show_title' => 'true', + 'compact' => 'false' + ), $atts, 'twp_browser_phone'); + + $show_title = ($atts['show_title'] === 'true'); + $compact_mode = ($atts['compact'] === 'true'); + + ob_start(); + ?> +
+ +

+ + + +
+ + Connecting... +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ + + + + + + + + + + + +
+
+ + +
+ + +
+ + + + + + + + + + + +
+
+