From 57859ebd20da6247e107b540ddc807ad19c4187d Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 23 May 2026 01:13:51 +0300 Subject: [PATCH] Deploy: 2026-05-23 01:13:51 --- backend/add_whatsapp_session_id_to_users.php | 49 + backend/app/Controllers/OTPController.php | 141 +++ backend/app/Controllers/StaffController.php | 184 +++ .../app/Controllers/SuperAdminController.php | 141 +++ .../app/Controllers/WhatsAppController.php | 140 ++- backend/app/Models/WhatsAppSession.php | 36 + backend/app/Services/TTSService.php | 48 + backend/check_db_data.php | 8 +- backend/public/admin.html | 1027 +++++++++++++++++ backend/public/index.html | 734 ++++++++++-- backend/public/index.php | 26 +- 11 files changed, 2426 insertions(+), 108 deletions(-) create mode 100644 backend/add_whatsapp_session_id_to_users.php create mode 100644 backend/app/Controllers/OTPController.php create mode 100644 backend/app/Controllers/StaffController.php create mode 100644 backend/app/Controllers/SuperAdminController.php create mode 100644 backend/app/Services/TTSService.php create mode 100644 backend/public/admin.html diff --git a/backend/add_whatsapp_session_id_to_users.php b/backend/add_whatsapp_session_id_to_users.php new file mode 100644 index 0000000..376f6bc --- /dev/null +++ b/backend/add_whatsapp_session_id_to_users.php @@ -0,0 +1,49 @@ +prepare(" + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'users' + AND COLUMN_NAME = 'whatsapp_session_id' + "); + $stmt->execute(); + $columnExists = $stmt->fetch(); + + if (!$columnExists) { + echo "Adding column 'whatsapp_session_id' to 'users' table...\n"; + + // Add column + $pdo->exec("ALTER TABLE `users` ADD COLUMN `whatsapp_session_id` INT NULL DEFAULT NULL"); + + // Add foreign key constraint + $pdo->exec(" + ALTER TABLE `users` + ADD CONSTRAINT `fk_users_whatsapp_session` + FOREIGN KEY (`whatsapp_session_id`) + REFERENCES `whatsapp_sessions` (`id`) + ON DELETE SET NULL + "); + + echo "✅ Column 'whatsapp_session_id' and foreign key created successfully!\n"; + } else { + echo "ℹ️ Column 'whatsapp_session_id' already exists in 'users' table. Skipping.\n"; + } + +} catch (\Exception $e) { + echo "❌ Migration failed: " . $e->getMessage() . "\n"; +} diff --git a/backend/app/Controllers/OTPController.php b/backend/app/Controllers/OTPController.php new file mode 100644 index 0000000..1da2e93 --- /dev/null +++ b/backend/app/Controllers/OTPController.php @@ -0,0 +1,141 @@ +company_id; + $body = $request->getBody(); + + $phone = $body['phone'] ?? ''; + $type = $body['type'] ?? 'text'; // 'text' or 'voice' + $sessionId = $body['session_id'] ?? null; + $customCode = $body['code'] ?? null; + + if (empty($phone)) { + $response->status(400)->json(['error' => 'Missing required parameter: phone']); + return; + } + + // Clean phone number (remove non-digits except +) + $phone = preg_replace('/[^\d+]/', '', $phone); + + // 1. Resolve WhatsApp Session + $session = null; + if ($sessionId) { + $session = WhatsAppSession::findSecure((int)$sessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(404)->json(['error' => 'WhatsApp session not found']); + return; + } + } else { + // Grab the first connected session of the company + $sessions = WhatsAppSession::findAllByCompany($companyId); + foreach ($sessions as $s) { + if ($s['status'] === 'connected') { + $session = $s; + break; + } + } + if (!$session && !empty($sessions)) { + $session = $sessions[0]; // fallback to first session if none is connected + } + } + + if (!$session) { + $response->status(400)->json(['error' => 'No active WhatsApp sessions configured for this company.']); + return; + } + + if ($session['status'] !== 'connected') { + $response->status(400)->json(['error' => 'WhatsApp session is not connected. Connect the session first.']); + return; + } + + // 2. Check SaaS subscription quotas + if ($companyId !== 1) { + $activeSub = CompanySubscription::findActiveByCompany($companyId); + if (!$activeSub) { + $response->status(402)->json(['error' => 'Active subscription plan required.']); + return; + } + + if (!CompanySubscriptionUsage::hasRemainingLimit($companyId, 'request')) { + $response->status(403)->json(['error' => 'Monthly request quota exceeded. Please upgrade your plan.']); + return; + } + + if ($type === 'voice') { + if (!CompanySubscriptionUsage::hasRemainingLimit($companyId, 'voice')) { + $response->status(403)->json(['error' => 'Voice request quota exceeded. Please upgrade your plan.']); + return; + } + + // Starter plan doesn't support Voice Notes + $features = json_decode($activeSub['features'] ?: '{}', true); + if (isset($features['voice']) && !$features['voice']) { + $response->status(403)->json(['error' => 'Voice OTP is not supported in your current subscription plan.']); + return; + } + } + } + + // 3. Generate verification code + $code = $customCode ? trim($customCode) : (string)rand(1000, 9999); + + // 4. Send Message + try { + if ($type === 'voice') { + // Spacing the digits to force slow Arabic pronunciation: e.g. "1 2 3 4" + $spacedCode = implode(' ', str_split($code)); + $textToRead = "رمز التحقق الخاص بك هو: {$spacedCode}. أكرر، رمز التحقق هو: {$spacedCode}."; + + $audioBase64 = TTSService::textToSpeechArabic($textToRead); + if (!$audioBase64) { + $response->status(500)->json(['error' => 'Failed to generate voice OTP audio.']); + return; + } + + // Send voice note + ConversationFlowEngine::sendReply($session, $phone, '', null, $audioBase64, 'audio/mp3'); + } else { + // Send text + $textMsg = "رمز التحقق الخاص بك لمتجر نابه هو: *{$code}* \n الرجاء عدم مشاركته مع أي شخص."; + ConversationFlowEngine::sendReply($session, $phone, $textMsg); + } + + // Increment usage stats + if ($companyId !== 1) { + CompanySubscriptionUsage::incrementUsage($companyId, 'request'); + if ($type === 'voice') { + CompanySubscriptionUsage::incrementUsage($companyId, 'voice'); + } + } + + $response->json([ + 'status' => 'success', + 'message' => 'OTP sent successfully', + 'code' => $code, + 'type' => $type + ]); + + } catch (\Exception $e) { + error_log("[OTP Controller Error] " . $e->getMessage()); + $response->status(500)->json(['error' => 'Failed to send OTP message: ' . $e->getMessage()]); + } + } +} diff --git a/backend/app/Controllers/StaffController.php b/backend/app/Controllers/StaffController.php new file mode 100644 index 0000000..a21ac87 --- /dev/null +++ b/backend/app/Controllers/StaffController.php @@ -0,0 +1,184 @@ +company_id; + + // Fetch users belonging to this company who are 'staff' + $staff = Database::select( + "SELECT u.id, u.name, u.email, u.role, u.status, u.whatsapp_session_id, w.name as session_name, w.phone as session_phone + FROM users u + LEFT JOIN whatsapp_sessions w ON u.whatsapp_session_id = w.id + WHERE u.company_id = ? AND u.role = 'staff' + ORDER BY u.id DESC", + [$companyId] + ); + + foreach ($staff as &$member) { + $member['email'] = Security::decrypt($member['email']); + if (!empty($member['session_phone'])) { + $member['session_phone'] = Security::decrypt($member['session_phone']); + } + } + + $response->json([ + 'status' => 'success', + 'data' => $staff + ]); + } + + /** + * Create a new customer service agent (staff) + * POST /api/staff + */ + public function store(Request $request, Response $response): void + { + $companyId = $request->company_id; + $errors = $this->validate($request, [ + 'name' => 'required|min:3', + 'email' => 'required|email', + 'password' => 'required|min:6' + ]); + + if (!empty($errors)) { + $response->json(['errors' => $errors], 400); + return; + } + + $body = $request->getBody(); + $email = strtolower(trim($body['email'])); + + // Check if user already exists + $existing = User::findByEmail($email); + if ($existing) { + $response->json(['errors' => ['email' => ['This email is already registered.']]], 409); + return; + } + + // Validate session if assigned + $whatsappSessionId = isset($body['whatsapp_session_id']) && $body['whatsapp_session_id'] !== '' ? (int)$body['whatsapp_session_id'] : null; + if ($whatsappSessionId) { + $session = WhatsAppSession::findSecure($whatsappSessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(400)->json(['error' => 'Invalid WhatsApp session assigned']); + return; + } + } + + try { + $userId = User::createSecure([ + 'company_id' => $companyId, + 'name' => trim($body['name']), + 'email' => $email, + 'password' => $body['password'], + 'role' => 'staff', + 'status' => 'active', + 'whatsapp_session_id' => $whatsappSessionId + ]); + + $response->json([ + 'status' => 'success', + 'message' => 'Agent created successfully', + 'data' => [ + 'id' => $userId, + 'name' => trim($body['name']), + 'email' => $email, + 'role' => 'staff', + 'whatsapp_session_id' => $whatsappSessionId + ] + ], 201); + } catch (\Exception $e) { + error_log("[Staff Controller Error] " . $e->getMessage()); + $response->status(500)->json(['error' => 'Failed to create agent: ' . $e->getMessage()]); + } + } + + /** + * Delete an agent + * DELETE /api/staff + */ + public function delete(Request $request, Response $response): void + { + $companyId = $request->company_id; + $body = $request->getBody(); + $agentId = $body['agent_id'] ?? null; + + if (!$agentId) { + $response->status(400)->json(['error' => 'Missing agent_id']); + return; + } + + $user = User::find($agentId); + if (!$user || (int)$user['company_id'] !== (int)$companyId || $user['role'] !== 'staff') { + $response->status(404)->json(['error' => 'Agent not found']); + return; + } + + User::delete((int)$agentId); + + $response->json([ + 'status' => 'success', + 'message' => 'Agent deleted successfully' + ]); + } + + /** + * Assign a specific WhatsApp session to an agent + * PUT /api/staff/assign + */ + public function assignSession(Request $request, Response $response): void + { + $companyId = $request->company_id; + $body = $request->getBody(); + + $agentId = $body['agent_id'] ?? null; + $whatsappSessionId = isset($body['whatsapp_session_id']) && $body['whatsapp_session_id'] !== '' ? (int)$body['whatsapp_session_id'] : null; + + if (!$agentId) { + $response->status(400)->json(['error' => 'Missing agent_id']); + return; + } + + $user = User::find($agentId); + if (!$user || (int)$user['company_id'] !== (int)$companyId || $user['role'] !== 'staff') { + $response->status(404)->json(['error' => 'Agent not found']); + return; + } + + if ($whatsappSessionId) { + $session = WhatsAppSession::findSecure($whatsappSessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(400)->json(['error' => 'Invalid WhatsApp session']); + return; + } + } + + try { + User::update((int)$agentId, [ + 'whatsapp_session_id' => $whatsappSessionId + ]); + + $response->json([ + 'status' => 'success', + 'message' => 'WhatsApp session successfully assigned to agent' + ]); + } catch (\Exception $e) { + $response->status(500)->json(['error' => 'Failed to assign session: ' . $e->getMessage()]); + } + } +} diff --git a/backend/app/Controllers/SuperAdminController.php b/backend/app/Controllers/SuperAdminController.php new file mode 100644 index 0000000..791ae66 --- /dev/null +++ b/backend/app/Controllers/SuperAdminController.php @@ -0,0 +1,141 @@ +company_id !== 1 || $request->role !== 'admin') { + $response->status(403)->json(['error' => 'Forbidden: Super Admin privileges required.']); + return false; + } + return true; + } + + /** + * Get platform statistics and companies list + * GET /api/admin/stats + */ + public function getStats(Request $request, Response $response): void + { + if (!$this->verifySuperAdmin($request, $response)) { + return; + } + + try { + // Overall stats + $companiesCount = Database::selectOne("SELECT COUNT(*) as count FROM companies")['count'] ?? 0; + $sessionsCount = Database::selectOne("SELECT COUNT(*) as count FROM whatsapp_sessions")['count'] ?? 0; + $connectedSessions = Database::selectOne("SELECT COUNT(*) as count FROM whatsapp_sessions WHERE status = 'connected'")['count'] ?? 0; + + // Detailed list of all companies and their current subscriptions + $companies = Database::select(" + SELECT + c.id, + c.name, + c.status, + cs.plan_id, + sp.name as plan_name, + cs.status as subscription_status, + cs.starts_at as subscription_starts, + cs.ends_at as subscription_ends, + (SELECT COUNT(*) FROM whatsapp_sessions WHERE company_id = c.id) as sessions_count, + (SELECT COUNT(*) FROM whatsapp_sessions WHERE company_id = c.id AND status = 'connected') as active_sessions, + COALESCE(cu.request_count, 0) as request_usage, + COALESCE(cu.voice_count, 0) as voice_usage, + COALESCE(cu.ocr_count, 0) as ocr_usage + FROM companies c + LEFT JOIN company_subscriptions cs ON cs.company_id = c.id AND cs.status = 'active' + LEFT JOIN subscription_plans sp ON cs.plan_id = sp.id + LEFT JOIN company_subscription_usage cu ON cu.company_id = c.id + AND cu.billing_start <= CURRENT_DATE() + AND cu.billing_end >= CURRENT_DATE() + ORDER BY c.id ASC + "); + + // Fetch list of available subscription plans + $plans = Database::select("SELECT id, name, price, max_sessions FROM subscription_plans ORDER BY price ASC"); + + $response->json([ + 'status' => 'success', + 'data' => [ + 'stats' => [ + 'total_companies' => (int)$companiesCount, + 'total_sessions' => (int)$sessionsCount, + 'connected_sessions' => (int)$connectedSessions + ], + 'companies' => $companies, + 'plans' => $plans + ] + ]); + + } catch (\Exception $e) { + error_log("[SuperAdminController Error] " . $e->getMessage()); + $response->status(500)->json(['error' => 'Failed to fetch platform stats: ' . $e->getMessage()]); + } + } + + /** + * Subscribe or upgrade a company to a plan + * POST /api/admin/companies/subscribe + */ + public function subscribeCompany(Request $request, Response $response): void + { + if (!$this->verifySuperAdmin($request, $response)) { + return; + } + + $body = $request->getBody(); + $targetCompanyId = isset($body['company_id']) ? (int)$body['company_id'] : null; + $planId = isset($body['plan_id']) ? (int)$body['plan_id'] : null; + $durationDays = isset($body['duration_days']) ? (int)$body['duration_days'] : 30; + + if (!$targetCompanyId || !$planId) { + $response->status(400)->json(['error' => 'Missing company_id or plan_id']); + return; + } + + // Verify company exists + $companyExists = Database::selectOne("SELECT id FROM companies WHERE id = ?", [$targetCompanyId]); + if (!$companyExists) { + $response->status(404)->json(['error' => 'Company not found']); + return; + } + + // Verify plan exists + $planExists = Database::selectOne("SELECT id FROM subscription_plans WHERE id = ?", [$planId]); + if (!$planExists) { + $response->status(404)->json(['error' => 'Subscription plan not found']); + return; + } + + try { + // Subscribe the company + $subId = CompanySubscription::subscribeCompany($targetCompanyId, $planId, $durationDays, 'manual_admin', 'admin_' . $request->user_id); + + // Clean active subscription cache for the company + if (class_exists('App\Core\Cache')) { + \App\Core\Cache::delete("company_subscription:{$targetCompanyId}"); + \App\Core\Cache::delete("company_subscription_{$targetCompanyId}"); + } + + $response->json([ + 'status' => 'success', + 'message' => 'Subscription updated successfully', + 'subscription_id' => $subId + ]); + } catch (\Exception $e) { + error_log("[SuperAdminController Error] " . $e->getMessage()); + $response->status(500)->json(['error' => 'Failed to update subscription: ' . $e->getMessage()]); + } + } +} diff --git a/backend/app/Controllers/WhatsAppController.php b/backend/app/Controllers/WhatsAppController.php index aeefc01..2cdfece 100644 --- a/backend/app/Controllers/WhatsAppController.php +++ b/backend/app/Controllers/WhatsAppController.php @@ -17,7 +17,17 @@ class WhatsAppController extends BaseController public function status(Request $request, Response $response) { $companyId = $request->company_id; // Added by AuthMiddleware - $session = WhatsAppSession::findOrCreate($companyId); + $sessionId = $request->get('session_id') ?? null; + + if ($sessionId) { + $session = WhatsAppSession::findSecure((int)$sessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(404)->json(['status' => 'error', 'message' => 'Session not found']); + return; + } + } else { + $session = WhatsAppSession::findOrCreate($companyId); + } // Auto-heal logic: Check if the session is active in the Node.js gateway if ($session['status'] === 'connected' && !empty($session['session_key'])) { @@ -85,12 +95,121 @@ class WhatsAppController extends BaseController } /** - * Request a new connection/QR code from the Baileys service + * Get all WhatsApp sessions for the company + */ + public function listSessions(Request $request, Response $response) + { + $companyId = $request->company_id; + $sessions = WhatsAppSession::findAllByCompany($companyId); + + $response->json([ + 'status' => 'success', + 'data' => $sessions + ]); + } + + /** + * Create a new WhatsApp session + */ + public function createSession(Request $request, Response $response) + { + $companyId = $request->company_id; + $body = $request->getBody(); + $name = !empty($body['name']) ? trim($body['name']) : 'WhatsApp Team'; + + // Fetch subscription limits + $activeSub = \App\Models\CompanySubscription::findActiveByCompany($companyId); + $maxSessions = 1; + if ($companyId === 1) { + $maxSessions = 10; + } elseif ($activeSub) { + $maxSessions = (int)$activeSub['max_sessions']; + } + + $sessions = WhatsAppSession::findAllByCompany($companyId); + if (count($sessions) >= $maxSessions) { + $response->status(400)->json([ + 'status' => 'error', + 'message' => "You have reached the maximum number of WhatsApp sessions allowed by your plan ({$maxSessions})." + ]); + return; + } + + $sessionKey = 'cmp_' . $companyId . '_' . bin2hex(random_bytes(4)); + $id = WhatsAppSession::create([ + 'company_id' => $companyId, + 'name' => $name, + 'session_key' => $sessionKey, + 'status' => 'disconnected' + ]); + + $newSession = WhatsAppSession::findSecure((int)$id); + + $response->json([ + 'status' => 'success', + 'data' => $newSession + ]); + } + + /** + * Delete an existing WhatsApp session + */ + public function deleteSession(Request $request, Response $response) + { + $companyId = $request->company_id; + $body = $request->getBody(); + $sessionId = $body['session_id'] ?? null; + + if (!$sessionId) { + $response->status(400)->json(['status' => 'error', 'message' => 'Missing session_id']); + return; + } + + $session = WhatsAppSession::findSecure((int)$sessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(404)->json(['status' => 'error', 'message' => 'Session not found']); + return; + } + + // Call Baileys Node.js Service to delete session from memory + $nodeUrl = 'http://127.0.0.1:3722/api/sessions/delete'; + $payload = json_encode(['session_key' => $session['session_key']]); + + $ch = curl_init($nodeUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'X-Webhook-Secret: ' . getenv('WEBHOOK_SECRET') + ]); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_exec($ch); + curl_close($ch); + + WhatsAppSession::delete((int)$sessionId); + + $response->json(['status' => 'success', 'message' => 'Session deleted successfully']); + } + + /** + * Request a new connection/QR code from the Baileys service for a specific session */ public function requestQr(Request $request, Response $response) { $companyId = $request->company_id; - $session = WhatsAppSession::findOrCreate($companyId); + $body = $request->getBody(); + $sessionId = $body['session_id'] ?? null; + + if ($sessionId) { + $session = WhatsAppSession::findSecure((int)$sessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(404)->json(['status' => 'error', 'message' => 'Session not found']); + return; + } + } else { + $session = WhatsAppSession::findOrCreate($companyId); + } // Temporarily set to connecting WhatsAppSession::updateState($session['id'], ['status' => 'connecting']); @@ -133,12 +252,23 @@ class WhatsAppController extends BaseController } /** - * Disconnect the current WhatsApp session + * Disconnect the WhatsApp session */ public function disconnect(Request $request, Response $response) { $companyId = $request->company_id; - $session = WhatsAppSession::findByCompany($companyId); + $body = $request->getBody(); + $sessionId = $body['session_id'] ?? null; + + if ($sessionId) { + $session = WhatsAppSession::findSecure((int)$sessionId); + if (!$session || (int)$session['company_id'] !== (int)$companyId) { + $response->status(404)->json(['status' => 'error', 'message' => 'Session not found']); + return; + } + } else { + $session = WhatsAppSession::findByCompany($companyId); + } if ($session && $session['status'] !== 'disconnected') { // Call Baileys Node.js Service to disconnect diff --git a/backend/app/Models/WhatsAppSession.php b/backend/app/Models/WhatsAppSession.php index 289a0d6..7068eac 100644 --- a/backend/app/Models/WhatsAppSession.php +++ b/backend/app/Models/WhatsAppSession.php @@ -32,6 +32,42 @@ class WhatsAppSession extends BaseModel return $session; } + /** + * Find secure session by ID + */ + public static function findSecure(int $id) + { + $session = Database::selectOne( + "SELECT * FROM " . static::$table . " WHERE id = ? LIMIT 1", + [$id] + ); + + if ($session) { + $session['phone'] = $session['phone'] ? Security::decrypt($session['phone']) : null; + $session['qr_code'] = $session['qr_code'] ? Security::decrypt($session['qr_code']) : null; + } + + return $session; + } + + /** + * Get all WhatsApp sessions for a company + */ + public static function findAllByCompany(int $companyId): array + { + $sessions = Database::select( + "SELECT * FROM " . static::$table . " WHERE company_id = ? ORDER BY id ASC", + [$companyId] + ); + + foreach ($sessions as &$session) { + $session['phone'] = $session['phone'] ? Security::decrypt($session['phone']) : null; + $session['qr_code'] = $session['qr_code'] ? Security::decrypt($session['qr_code']) : null; + } + + return $sessions; + } + /** * Get a session by session_key (used by webhooks) */ diff --git a/backend/app/Services/TTSService.php b/backend/app/Services/TTSService.php new file mode 100644 index 0000000..36e4705 --- /dev/null +++ b/backend/app/Services/TTSService.php @@ -0,0 +1,48 @@ +getMessage()); + return null; + } + } +} diff --git a/backend/check_db_data.php b/backend/check_db_data.php index 047a4b4..6dc895a 100644 --- a/backend/check_db_data.php +++ b/backend/check_db_data.php @@ -12,7 +12,7 @@ try { $pdo = Database::getConnection(); echo "=== 1. Subscription Plans ===\n"; - $plans = Database::selectAll("SELECT * FROM subscription_plans"); + $plans = Database::select("SELECT * FROM subscription_plans"); if (empty($plans)) { echo "No subscription plans found.\n"; } else { @@ -22,7 +22,7 @@ try { } echo "\n=== 2. Companies ===\n"; - $companies = Database::selectAll("SELECT * FROM companies LIMIT 10"); + $companies = Database::select("SELECT * FROM companies LIMIT 10"); if (empty($companies)) { echo "No companies found.\n"; } else { @@ -32,7 +32,7 @@ try { } echo "\n=== 3. Active Company Subscriptions ===\n"; - $subs = Database::selectAll(" + $subs = Database::select(" SELECT cs.*, p.name as plan_name FROM company_subscriptions cs JOIN subscription_plans p ON cs.plan_id = p.id @@ -46,7 +46,7 @@ try { } echo "\n=== 4. WooCommerce Stores ===\n"; - $stores = Database::selectAll("SELECT id, company_id, store_url, is_active, webhook_secret FROM woocommerce_stores"); + $stores = Database::select("SELECT id, company_id, store_url, is_active, webhook_secret FROM woocommerce_stores"); if (empty($stores)) { echo "No WooCommerce stores found.\n"; } else { diff --git a/backend/public/admin.html b/backend/public/admin.html new file mode 100644 index 0000000..9d18ab6 --- /dev/null +++ b/backend/public/admin.html @@ -0,0 +1,1027 @@ + + + + + + نابه | لوحة التحكم للمشرف العام + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ + +
+ + +
+
+
+ إجمالي الشركات + - +
+
+ + + +
+
+ +
+
+ الجلسات المتصلة + - +
+
+ + + +
+
+ +
+
+ حالة النظام + مستقر +
+
+ + + +
+
+
+ + +
+
+
+ + + + قائمة الشركات والاشتراكات +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + +
IDالشركةالباقةالأرقام النشطةرسائل الـ APIالرسائل الصوتيةتاريخ الانتهاءإجراءات
لا توجد نتائج مطابقة للبحث
+
+
+
+ + + + + +
+ + + + + + + +
+ + + + + diff --git a/backend/public/index.html b/backend/public/index.html index 57817f8..ef339b6 100644 --- a/backend/public/index.html +++ b/backend/public/index.html @@ -774,9 +774,12 @@ - + @@ -797,107 +800,159 @@
-

WhatsApp Integration

- -
- -
-
- -
- -

- - -

- -

- - -

- - - - - +
+

+
+ +
+
- -
- +

-