@@ -1422,6 +1650,29 @@
// Dashboard States
activeDashboardTab: 'whatsapp',
whatsappSession: null,
+ whatsappSessions: [],
+ newSessionName: '',
+ staff: [],
+ showAddStaffModal: false,
+ staffForm: {
+ name: '',
+ email: '',
+ password: '',
+ whatsapp_session_id: ''
+ },
+ woocommerceStatus: null,
+ woocommerceLoading: false,
+ wooForm: {
+ store_url: '',
+ consumer_key: '',
+ consumer_secret: '',
+ webhook_secret: ''
+ },
+ otpPhone: '',
+ otpType: 'text',
+ otpSessionId: '',
+ otpStatusMsg: '',
+ otpErrorCode: '',
contacts: [],
selectedContactIds: [],
bulkGroupId: '',
@@ -1582,6 +1833,9 @@
this.user = null;
this.isLoggedIn = false;
this.whatsappSession = null;
+ this.whatsappSessions = [];
+ this.staff = [];
+ this.woocommerceStatus = null;
this.contacts = [];
this.templates = [];
this.campaigns = [];
@@ -1592,8 +1846,10 @@
},
initializeDashboard() {
+ this.fetchWhatsappSessions();
this.fetchWhatsappStatus();
this.fetchSallaStatus();
+ this.fetchWooCommerceStatus();
// Set up persistent background status check
this.startPolling();
@@ -1615,8 +1871,9 @@
async fetchWhatsappStatus() {
if (!this.token) return;
+ const queryParam = this.whatsappSession ? `?session_id=${this.whatsappSession.id}` : '';
try {
- const response = await fetch('/api/whatsapp/status', {
+ const response = await fetch(`/api/whatsapp/status${queryParam}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
@@ -1625,13 +1882,88 @@
if (this.whatsappSession && this.whatsappSession.status === 'waiting_qr') {
this.$nextTick(() => this.renderQr());
}
+
+ if (this.whatsappSessions && this.whatsappSessions.length > 0 && this.whatsappSession) {
+ const idx = this.whatsappSessions.findIndex(s => s.id === this.whatsappSession.id);
+ if (idx !== -1) {
+ this.whatsappSessions[idx] = this.whatsappSession;
+ }
+ }
}
} catch (err) {
console.error('Failed to retrieve session status:', err);
}
},
- async connectWhatsapp() {
+ async fetchWhatsappSessions() {
+ if (!this.token) return;
+ try {
+ const response = await fetch('/api/whatsapp/sessions', {
+ headers: { 'Authorization': `Bearer ${this.token}` }
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.whatsappSessions = data.data || [];
+ }
+ } catch (err) {
+ console.error('Failed to retrieve sessions list:', err);
+ }
+ },
+
+ async createWhatsappSession() {
+ this.actionLoading = true;
+ try {
+ const response = await fetch('/api/whatsapp/sessions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify({ name: this.newSessionName })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.newSessionName = '';
+ await this.fetchWhatsappSessions();
+ } else {
+ alert(data.message || 'Failed to create WhatsApp session');
+ }
+ } catch (err) {
+ alert('Error communicating with backend Gateway API.');
+ } finally {
+ this.actionLoading = false;
+ }
+ },
+
+ async deleteWhatsappSession(sessionId) {
+ if (!confirm('Are you sure you want to delete this WhatsApp session? This will remove all associated connection settings.')) return;
+ this.actionLoading = true;
+ try {
+ const response = await fetch('/api/whatsapp/sessions', {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify({ session_id: sessionId })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ if (this.whatsappSession && this.whatsappSession.id === sessionId) {
+ this.whatsappSession = null;
+ }
+ await this.fetchWhatsappSessions();
+ } else {
+ alert(data.message || 'Failed to delete session');
+ }
+ } catch (err) {
+ console.error('Error deleting session:', err);
+ } finally {
+ this.actionLoading = false;
+ }
+ },
+
+ async connectWhatsapp(sessionId) {
this.actionLoading = true;
try {
const response = await fetch('/api/whatsapp/qr', {
@@ -1639,10 +1971,16 @@
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
- }
+ },
+ body: JSON.stringify({ session_id: sessionId })
});
const data = await response.json();
if (response.ok && data.status === 'success') {
+ const found = this.whatsappSessions.find(s => s.id === sessionId);
+ if (found) {
+ this.whatsappSession = found;
+ this.whatsappSession.status = 'connecting';
+ }
await this.fetchWhatsappStatus();
} else {
alert(data.message || 'Failed to initialize session');
@@ -1654,8 +1992,8 @@
}
},
- async disconnectWhatsapp() {
- if (!confirm('Are you sure you want to disconnect your WhatsApp link?')) return;
+ async disconnectWhatsapp(sessionId) {
+ if (!confirm('Are you sure you want to disconnect this WhatsApp link?')) return;
this.actionLoading = true;
try {
const response = await fetch('/api/whatsapp/disconnect', {
@@ -1663,14 +2001,214 @@
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ session_id: sessionId })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ await this.fetchWhatsappSessions();
+ if (this.whatsappSession && this.whatsappSession.id === sessionId) {
+ await this.fetchWhatsappStatus();
+ }
+ }
+ } catch (err) {
+ console.error(err);
+ } finally {
+ this.actionLoading = false;
+ }
+ },
+
+ // Customer Service Staff methods
+ async fetchStaff() {
+ this.staffLoading = true;
+ try {
+ const response = await fetch('/api/staff', {
+ headers: { 'Authorization': `Bearer ${this.token}` }
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.staff = data.data || [];
+ }
+ } catch (err) {
+ console.error('Error fetching staff list:', err);
+ } finally {
+ this.staffLoading = false;
+ }
+ },
+
+ async submitAddStaff() {
+ this.actionLoading = true;
+ try {
+ const response = await fetch('/api/staff', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify(this.staffForm)
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.showAddStaffModal = false;
+ this.staffForm = { name: '', email: '', password: '', whatsapp_session_id: '' };
+ await this.fetchStaff();
+ } else {
+ const errs = data.errors || {};
+ const firstErr = Object.values(errs)[0]?.[0] || data.error || 'Failed to create agent';
+ alert(firstErr);
+ }
+ } catch (err) {
+ alert('Network error while adding agent');
+ } finally {
+ this.actionLoading = false;
+ }
+ },
+
+ async deleteStaff(agentId) {
+ if (!confirm('Are you sure you want to remove this customer service agent?')) return;
+ this.actionLoading = true;
+ try {
+ const response = await fetch('/api/staff', {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify({ agent_id: agentId })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ await this.fetchStaff();
+ } else {
+ alert(data.error || 'Failed to delete agent');
+ }
+ } catch (err) {
+ console.error(err);
+ } finally {
+ this.actionLoading = false;
+ }
+ },
+
+ async assignSessionToStaff(agentId, sessionId) {
+ try {
+ const response = await fetch('/api/staff/assign', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify({
+ agent_id: agentId,
+ whatsapp_session_id: sessionId ? parseInt(sessionId) : null
+ })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ await this.fetchStaff();
+ } else {
+ alert(data.error || 'Failed to assign session');
+ }
+ } catch (err) {
+ console.error('Error assigning session:', err);
+ }
+ },
+
+ // WooCommerce Integration methods
+ async fetchWooCommerceStatus() {
+ try {
+ const response = await fetch('/api/integrations/woocommerce/status', {
+ headers: { 'Authorization': `Bearer ${this.token}` }
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.woocommerceStatus = data;
+ }
+ } catch (err) {
+ console.error('Error fetching WooCommerce status:', err);
+ }
+ },
+
+ async connectWooCommerce() {
+ this.woocommerceLoading = true;
+ try {
+ const response = await fetch('/api/integrations/woocommerce/connect', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify(this.wooForm)
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ await this.fetchWooCommerceStatus();
+ } else {
+ alert(data.message || 'Failed to connect WooCommerce store');
+ }
+ } catch (err) {
+ alert('Network error while connecting WooCommerce');
+ } finally {
+ this.woocommerceLoading = false;
+ }
+ },
+
+ async disconnectWooCommerce() {
+ if (!confirm('Are you sure you want to disconnect WooCommerce integration? Webhooks will no longer notify customers.')) return;
+ this.woocommerceLoading = true;
+ try {
+ const response = await fetch('/api/integrations/woocommerce/disconnect', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
}
});
const data = await response.json();
if (response.ok && data.status === 'success') {
- await this.fetchWhatsappStatus();
+ this.wooForm = { store_url: '', consumer_key: '', consumer_secret: '', webhook_secret: '' };
+ await this.fetchWooCommerceStatus();
}
} catch (err) {
console.error(err);
+ } finally {
+ this.woocommerceLoading = false;
+ }
+ },
+
+ // OTP Testing Tool methods
+ async sendOtpTest() {
+ if (!this.otpSessionId) {
+ alert('Please select a WhatsApp line to send from.');
+ return;
+ }
+ this.actionLoading = true;
+ this.otpStatusMsg = '';
+ this.otpErrorCode = '';
+ try {
+ const response = await fetch('/api/otp/send', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.token}`
+ },
+ body: JSON.stringify({
+ phone: this.otpPhone,
+ type: this.otpType,
+ session_id: parseInt(this.otpSessionId)
+ })
+ });
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ this.otpStatusMsg = this.lang === 'ar'
+ ? `✓ تم إرسال رمز التحقق بنجاح! الرمز المرسل هو: ${data.code}`
+ : `✓ OTP delivered successfully! Code sent: ${data.code}`;
+ } else {
+ this.otpErrorCode = 'error';
+ this.otpStatusMsg = data.error || 'Failed to deliver OTP';
+ }
+ } catch (err) {
+ this.otpErrorCode = 'error';
+ this.otpStatusMsg = 'Network error while delivering OTP';
} finally {
this.actionLoading = false;
}
diff --git a/backend/public/index.php b/backend/public/index.php
index 80491be..09f1eaa 100644
--- a/backend/public/index.php
+++ b/backend/public/index.php
@@ -28,6 +28,14 @@ $router->get('/', function ($request, $response) {
exit;
});
+// Serve admin.html super admin panel on /admin path
+$router->get('/admin', function ($request, $response) {
+ $response->setHeader('Content-Type', 'text/html; charset=utf-8');
+ $response->sendHeaders();
+ readfile(__DIR__ . '/admin.html');
+ exit;
+});
+
// Health Check — no php_version or environment in production to avoid info disclosure
$router->get('/api/health', function ($request, $response) {
$response->json([
@@ -43,12 +51,28 @@ $router->post('/api/auth/register', [\App\Controllers\AuthController::class, 're
$router->post('/api/auth/login', [\App\Controllers\AuthController::class, 'login'], [\App\Middlewares\RateLimitMiddleware::class]);
$router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me'], [\App\Middlewares\AuthMiddleware::class]);
-// WhatsApp Gateway Routes
+// WhatsApp Gateway & Multi-Session Routes
$router->get('/api/whatsapp/status', [\App\Controllers\WhatsAppController::class, 'status'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/whatsapp/qr', [\App\Controllers\WhatsAppController::class, 'requestQr'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/whatsapp/disconnect', [\App\Controllers\WhatsAppController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class]);
+$router->get('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'listSessions'], [\App\Middlewares\AuthMiddleware::class]);
+$router->post('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'createSession'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
+$router->delete('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'deleteSession'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/whatsapp/webhook', [\App\Controllers\WhatsAppController::class, 'webhook']); // No AuthMiddleware (Protected by WEBHOOK_SECRET internally)
+// Customer Service Agents (Staff) Routes
+$router->get('/api/staff', [\App\Controllers\StaffController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
+$router->post('/api/staff', [\App\Controllers\StaffController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
+$router->delete('/api/staff', [\App\Controllers\StaffController::class, 'delete'], [\App\Middlewares\AuthMiddleware::class]);
+$router->put('/api/staff/assign', [\App\Controllers\StaffController::class, 'assignSession'], [\App\Middlewares\AuthMiddleware::class]);
+
+// Text and Voice OTP Verification Routes
+$router->post('/api/otp/send', [\App\Controllers\OTPController::class, 'send'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
+
+// Super Admin Routes
+$router->get('/api/admin/stats', [\App\Controllers\SuperAdminController::class, 'getStats'], [\App\Middlewares\AuthMiddleware::class]);
+$router->post('/api/admin/companies/subscribe', [\App\Controllers\SuperAdminController::class, 'subscribeCompany'], [\App\Middlewares\AuthMiddleware::class]);
+
// Phase 4 & 5: CRM, Templates & Campaigns Routes
$router->get('/api/contacts', [\App\Controllers\ContactController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/contacts', [\App\Controllers\ContactController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);