Add comprehensive voicemail integration to browser phone
- Add AJAX endpoint for fetching user voicemails with stats and recent list - Create voicemail display section with counts, transcriptions, and playback - Implement click-to-play functionality for voicemail audio - Add refresh and view-all buttons with admin page integration - Style voicemail section with consistent design and dark mode support - Include visual indicators for recordings and formatted durations - Add mobile responsive design and hover effects 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		@@ -3770,6 +3770,56 @@ class TWP_Admin {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * AJAX handler for getting user's recent voicemails
 | 
			
		||||
     */
 | 
			
		||||
    public function ajax_get_user_voicemails() {
 | 
			
		||||
        check_ajax_referer('twp_ajax_nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        if (!current_user_can('manage_options') && !current_user_can('twp_access_voicemails')) {
 | 
			
		||||
            wp_send_json_error('Unauthorized');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        $table_name = $wpdb->prefix . 'twp_voicemails';
 | 
			
		||||
        
 | 
			
		||||
        // Get recent voicemails (last 10)
 | 
			
		||||
        $voicemails = $wpdb->get_results($wpdb->prepare("
 | 
			
		||||
            SELECT id, from_number, duration, transcription, created_at, recording_url
 | 
			
		||||
            FROM $table_name 
 | 
			
		||||
            ORDER BY created_at DESC 
 | 
			
		||||
            LIMIT %d
 | 
			
		||||
        ", 10));
 | 
			
		||||
        
 | 
			
		||||
        // Format data for frontend
 | 
			
		||||
        $formatted_voicemails = array();
 | 
			
		||||
        foreach ($voicemails as $vm) {
 | 
			
		||||
            $formatted_voicemails[] = array(
 | 
			
		||||
                'id' => $vm->id,
 | 
			
		||||
                'from_number' => $vm->from_number,
 | 
			
		||||
                'duration' => $vm->duration,
 | 
			
		||||
                'transcription' => $vm->transcription ? substr($vm->transcription, 0, 100) . '...' : 'No transcription',
 | 
			
		||||
                'created_at' => $vm->created_at,
 | 
			
		||||
                'time_ago' => human_time_diff(strtotime($vm->created_at), current_time('timestamp')) . ' ago',
 | 
			
		||||
                'has_recording' => !empty($vm->recording_url)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get voicemail counts
 | 
			
		||||
        $total_count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
 | 
			
		||||
        $today_count = $wpdb->get_var($wpdb->prepare("
 | 
			
		||||
            SELECT COUNT(*) FROM $table_name 
 | 
			
		||||
            WHERE DATE(created_at) = %s
 | 
			
		||||
        ", current_time('Y-m-d')));
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json_success(array(
 | 
			
		||||
            'voicemails' => $formatted_voicemails,
 | 
			
		||||
            'total_count' => $total_count,
 | 
			
		||||
            'today_count' => $today_count
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * AJAX handler for getting all groups
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -463,6 +463,148 @@
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Voicemail Section */
 | 
			
		||||
.twp-voicemail-section {
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    padding: 16px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    border: 2px solid #e9ecef;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.twp-voicemail-section h4 {
 | 
			
		||||
    margin: 0 0 16px 0;
 | 
			
		||||
    color: #212529;
 | 
			
		||||
    font-size: 1.1rem;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-stats {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-around;
 | 
			
		||||
    margin-bottom: 16px;
 | 
			
		||||
    padding: 12px;
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-item {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-value {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.twp-voicemail-list {
 | 
			
		||||
    margin-bottom: 16px;
 | 
			
		||||
    max-height: 200px;
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-item {
 | 
			
		||||
    padding: 12px;
 | 
			
		||||
    border: 1px solid #dee2e6;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: all 0.2s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-item:hover {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    border-color: #007bff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-item.has-recording {
 | 
			
		||||
    border-left: 4px solid #28a745;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-from {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.from-number {
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-time {
 | 
			
		||||
    font-size: 0.85rem;
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-details {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 12px;
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-duration {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 4px;
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.recording-indicator {
 | 
			
		||||
    background: #28a745;
 | 
			
		||||
    color: white;
 | 
			
		||||
    padding: 2px 8px;
 | 
			
		||||
    border-radius: 12px;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-transcription {
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    border-left: 3px solid #007bff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-actions {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.voicemail-actions .twp-btn {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    min-width: 80px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-voicemails,
 | 
			
		||||
.voicemail-loading {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.twp-btn-secondary {
 | 
			
		||||
    background: #6c757d;
 | 
			
		||||
    color: white;
 | 
			
		||||
@@ -588,7 +730,8 @@
 | 
			
		||||
    .twp-connection-status,
 | 
			
		||||
    .twp-call-info,
 | 
			
		||||
    .twp-queue-controls,
 | 
			
		||||
    .twp-queue-section {
 | 
			
		||||
    .twp-queue-section,
 | 
			
		||||
    .twp-voicemail-section {
 | 
			
		||||
        background: #2d3748;
 | 
			
		||||
        border-color: #4a5568;
 | 
			
		||||
    }
 | 
			
		||||
@@ -623,8 +766,11 @@
 | 
			
		||||
    .twp-phone-selection label,
 | 
			
		||||
    .twp-browser-phone-title,
 | 
			
		||||
    .twp-queue-section h4,
 | 
			
		||||
    .twp-voicemail-section h4,
 | 
			
		||||
    .selected-queue-info h5,
 | 
			
		||||
    .timer-value {
 | 
			
		||||
    .timer-value,
 | 
			
		||||
    .from-number,
 | 
			
		||||
    .stat-value {
 | 
			
		||||
        color: #f7fafc;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -635,14 +781,33 @@
 | 
			
		||||
    .queue-info,
 | 
			
		||||
    .queue-stats,
 | 
			
		||||
    .queue-loading,
 | 
			
		||||
    .no-queues {
 | 
			
		||||
    .no-queues,
 | 
			
		||||
    .voicemail-stats,
 | 
			
		||||
    .voicemail-time,
 | 
			
		||||
    .voicemail-duration,
 | 
			
		||||
    .stat-label,
 | 
			
		||||
    .voicemail-loading,
 | 
			
		||||
    .no-voicemails {
 | 
			
		||||
        color: #cbd5e0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .no-queues {
 | 
			
		||||
    .no-queues,
 | 
			
		||||
    .voicemail-stats,
 | 
			
		||||
    .voicemail-transcription {
 | 
			
		||||
        background: #2d3748;
 | 
			
		||||
        border-color: #4a5568;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .voicemail-item {
 | 
			
		||||
        background: #2d3748;
 | 
			
		||||
        border-color: #4a5568;
 | 
			
		||||
        color: #f7fafc;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .voicemail-item:hover {
 | 
			
		||||
        background: #4a5568;
 | 
			
		||||
        border-color: #63b3ed;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Animation for incoming calls */
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@
 | 
			
		||||
        bindEvents();
 | 
			
		||||
        loadPhoneNumbers();
 | 
			
		||||
        loadUserQueues();
 | 
			
		||||
        loadUserVoicemails();
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -281,6 +282,23 @@
 | 
			
		||||
            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 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');
 | 
			
		||||
@@ -921,6 +939,119 @@
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 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');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 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');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Load alert preference on init
 | 
			
		||||
    loadAlertPreference();
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -156,6 +156,7 @@ class TWP_Core {
 | 
			
		||||
        $this->loader->add_action('wp_ajax_twp_delete_voicemail', $plugin_admin, 'ajax_delete_voicemail');
 | 
			
		||||
        $this->loader->add_action('wp_ajax_twp_transcribe_voicemail', $plugin_admin, 'ajax_transcribe_voicemail');
 | 
			
		||||
        $this->loader->add_action('wp_ajax_twp_get_voicemail_audio', $plugin_admin, 'ajax_get_voicemail_audio');
 | 
			
		||||
        $this->loader->add_action('wp_ajax_twp_get_user_voicemails', $plugin_admin, 'ajax_get_user_voicemails');
 | 
			
		||||
        
 | 
			
		||||
        // Agent group management AJAX
 | 
			
		||||
        $this->loader->add_action('wp_ajax_twp_get_all_groups', $plugin_admin, 'ajax_get_all_groups');
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,7 @@ class TWP_Shortcodes {
 | 
			
		||||
            // Localize script with AJAX data
 | 
			
		||||
            wp_localize_script('twp-browser-phone-frontend', 'twp_frontend_ajax', array(
 | 
			
		||||
                'ajax_url' => admin_url('admin-ajax.php'),
 | 
			
		||||
                'admin_url' => admin_url(),
 | 
			
		||||
                'nonce' => wp_create_nonce('twp_frontend_nonce'),
 | 
			
		||||
                'user_id' => get_current_user_id(),
 | 
			
		||||
                'is_logged_in' => is_user_logged_in()
 | 
			
		||||
@@ -200,6 +201,34 @@ class TWP_Shortcodes {
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Voicemail Section -->
 | 
			
		||||
            <div class="twp-voicemail-section" id="twp-voicemail-section">
 | 
			
		||||
                <h4>Recent Voicemails</h4>
 | 
			
		||||
                <div class="voicemail-stats">
 | 
			
		||||
                    <div class="stat-item">
 | 
			
		||||
                        <span class="stat-label">Total:</span>
 | 
			
		||||
                        <span class="stat-value" id="twp-total-voicemails">0</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="stat-item">
 | 
			
		||||
                        <span class="stat-label">Today:</span>
 | 
			
		||||
                        <span class="stat-value" id="twp-today-voicemails">0</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="twp-voicemail-list" id="twp-voicemail-list">
 | 
			
		||||
                    <div class="voicemail-loading">Loading voicemails...</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="voicemail-actions">
 | 
			
		||||
                    <button id="twp-refresh-voicemails" class="twp-btn twp-btn-secondary">
 | 
			
		||||
                        Refresh
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button id="twp-view-all-voicemails" class="twp-btn twp-btn-secondary">
 | 
			
		||||
                        View All
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Audio elements for incoming calls handled by browser -->
 | 
			
		||||
            <audio id="twp-ringtone" style="display: none;"></audio>
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user