Files
nabeh/backend/public/index.html
2026-05-23 02:22:31 +03:00

2839 lines
165 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nabeh Hub — WhatsApp Gateway & CRM</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta name="description" content="Connect, manage, and scale your WhatsApp communication.">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- QR Code Generator Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- AlpineJS CDN -->
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
:root {
--bg-color: #0b0d19;
--card-bg: rgba(20, 24, 46, 0.65);
--card-border: rgba(255, 255, 255, 0.07);
--text-primary: #f3f4f6;
--text-secondary: #9ca3af;
--primary-accent: #06b6d4; /* Cyan */
--primary-accent-glow: rgba(6, 182, 212, 0.4);
--success-accent: #10b981; /* Emerald */
--success-glow: rgba(16, 185, 129, 0.3);
--warning-accent: #f59e0b; /* Amber */
--warning-glow: rgba(245, 158, 11, 0.3);
--danger-accent: #ef4444; /* Rose */
--danger-glow: rgba(239, 68, 68, 0.3);
--sidebar-width: 260px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
min-height: 100vh;
overflow-x: hidden;
display: flex;
flex-direction: column;
position: relative;
}
/* Abstract glowing background patterns */
body::before {
content: '';
position: absolute;
width: 350px;
height: 350px;
top: 15%;
left: 10%;
background: radial-gradient(circle, rgba(99, 102, 241, 0.15) 0%, transparent 70%);
z-index: -1;
pointer-events: none;
}
body::after {
content: '';
position: absolute;
width: 450px;
height: 450px;
bottom: 10%;
right: 5%;
background: radial-gradient(circle, var(--primary-accent-glow) 0%, transparent 70%);
z-index: -1;
pointer-events: none;
}
h1, h2, h3, h4 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
}
a, button, input {
font-family: inherit;
}
/* Container & Layout */
.app-container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
flex: 1;
display: flex;
flex-direction: column;
}
/* Glassmorphism Auth Card */
.auth-wrapper {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
min-height: 80vh;
}
.auth-card {
background: var(--card-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--card-border);
border-radius: 20px;
padding: 2.5rem;
width: 100%;
max-width: 460px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
animation: fadeIn 0.5s ease-out;
}
.brand-header {
text-align: center;
margin-bottom: 2rem;
}
.brand-logo {
font-size: 2.2rem;
font-weight: 800;
background: linear-gradient(135deg, #fff 30%, var(--primary-accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.05em;
margin-bottom: 0.5rem;
}
.brand-subtitle {
font-size: 0.9rem;
color: var(--text-secondary);
}
/* Tabs styling */
.tabs-header {
display: flex;
border-bottom: 1px solid var(--card-border);
margin-bottom: 1.5rem;
}
.tab-btn {
flex: 1;
padding: 0.8rem;
text-align: center;
background: none;
border: none;
color: var(--text-secondary);
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.tab-btn.active {
color: var(--text-primary);
}
.tab-btn.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background: var(--primary-accent);
box-shadow: 0 0 10px var(--primary-accent-glow);
}
/* Inputs & Form control */
.form-group {
margin-bottom: 1.25rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 500;
}
.form-input {
width: 100%;
padding: 0.75rem 1rem;
background: rgba(10, 11, 20, 0.5);
border: 1px solid var(--card-border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.95rem;
transition: all 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: var(--primary-accent);
box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.15);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.8rem 1.5rem;
font-weight: 600;
border-radius: 8px;
border: none;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.95rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-accent) 0%, #0891b2 100%);
color: #fff;
box-shadow: 0 4px 14px var(--primary-accent-glow);
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px var(--primary-accent-glow);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.08);
color: var(--text-primary);
border: 1px solid var(--card-border);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.12);
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-accent) 0%, #dc2626 100%);
color: #fff;
box-shadow: 0 4px 14px var(--danger-glow);
}
.btn-danger:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px var(--danger-glow);
}
/* Message banners */
.banner {
padding: 0.75rem 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-size: 0.85rem;
display: flex;
align-items: center;
animation: slideDown 0.3s ease-out;
}
.banner-danger {
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #fca5a5;
}
.banner-success {
background: rgba(16, 185, 129, 0.15);
border: 1px solid rgba(16, 185, 129, 0.3);
color: #d1fae5;
}
/* Dashboard layout */
.dashboard-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--card-border);
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.user-info {
display: flex;
align-items: center;
gap: 0.75rem;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-accent) 0%, #4f46e5 100%);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-family: 'Outfit', sans-serif;
color: #fff;
}
.dashboard-layout {
display: grid;
grid-template-columns: 240px 1fr;
gap: 2rem;
flex: 1;
}
@media (max-width: 900px) {
.dashboard-layout {
grid-template-columns: 1fr;
}
}
/* Navigation menu */
.nav-menu {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
@media (max-width: 900px) {
.nav-menu {
flex-direction: row;
overflow-x: auto;
padding-bottom: 0.5rem;
}
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.8rem 1rem;
border-radius: 10px;
background: none;
border: none;
color: var(--text-secondary);
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
}
.nav-item:hover, .nav-item.active {
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
}
.nav-item.active {
box-shadow: inset 3px 0 0 var(--primary-accent);
background: rgba(6, 182, 212, 0.08);
}
@media (max-width: 900px) {
.nav-item.active {
box-shadow: inset 0 -3px 0 var(--primary-accent);
}
}
/* Dashboard content panels */
.panel {
background: var(--card-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
animation: fadeIn 0.4s ease-out;
}
.grid-two {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
@media (max-width: 768px) {
.grid-two {
grid-template-columns: 1fr;
}
}
/* Status indicators */
.status-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
border-radius: 12px;
background: rgba(10, 11, 20, 0.4);
border: 1px solid var(--card-border);
text-align: center;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1.5rem;
}
.badge-disconnected {
background: rgba(239, 68, 68, 0.12);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.25);
}
.badge-connecting {
background: rgba(6, 182, 212, 0.12);
color: #22d3ee;
border: 1px solid rgba(6, 182, 212, 0.25);
animation: pulse 1.5s infinite alternate;
}
.badge-waiting_qr {
background: rgba(245, 158, 11, 0.12);
color: #fbbf24;
border: 1px solid rgba(245, 158, 11, 0.25);
animation: pulse-border 1.5s infinite;
}
.badge-connected {
background: rgba(16, 185, 129, 0.12);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.25);
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
}
/* QR Code wrapper */
.qr-wrapper {
background: #fff;
padding: 1.25rem;
border-radius: 12px;
display: inline-flex;
justify-content: center;
align-items: center;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
margin: 1.5rem 0;
position: relative;
}
/* Lists and Tables */
.data-table-container {
width: 100%;
overflow-x: auto;
margin-top: 1rem;
}
.data-table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
.data-table th {
padding: 1rem;
border-bottom: 1px solid var(--card-border);
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
}
.data-table td {
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
font-size: 0.9rem;
}
.data-table tr:hover td {
background: rgba(255, 255, 255, 0.02);
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-secondary);
}
/* Spinner / Loading styles */
.spinner {
border: 3px solid rgba(255, 255, 255, 0.1);
width: 24px;
height: 24px;
border-radius: 50%;
border-left-color: var(--primary-accent);
animation: spin 0.8s linear infinite;
display: inline-block;
}
.spinner-large {
width: 48px;
height: 48px;
border-width: 4px;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes pulse {
0% { opacity: 0.6; }
100% { opacity: 1; }
}
@keyframes pulse-border {
0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); }
100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); }
}
/* Utility classes */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.gap-1 { gap: 0.25rem; }
.text-center { text-align: center; }
.font-semibold { font-weight: 600; }
.text-muted { color: var(--text-secondary); }
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(10, 11, 20, 0.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
animation: fadeIn 0.3s ease-out;
}
.modal-card {
background: var(--card-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--card-border);
border-radius: 16px;
width: 100%;
max-width: 500px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
animation: slideDown 0.3s ease-out;
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--card-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-size: 1.2rem;
font-weight: 700;
}
.modal-close {
background: none;
border: none;
color: var(--text-secondary);
font-size: 1.5rem;
cursor: pointer;
transition: color 0.2s ease;
}
.modal-close:hover {
color: var(--text-primary);
}
.modal-body {
padding: 1.5rem;
overflow-y: auto;
max-height: 70vh;
}
.modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--card-border);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.font-semibold { font-weight: 600; }
.text-muted { color: var(--text-secondary); }
@keyframes pulse-red {
0% { transform: scale(0.85); opacity: 0.5; }
50% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(0.85); opacity: 0.5; }
}
.recording-pulse {
animation: pulse-red 1.2s infinite;
}
/* RTL Overrides */
body[dir="rtl"] .nav-item {
text-align: right;
flex-direction: row;
}
body[dir="rtl"] .data-table {
text-align: right;
}
body[dir="rtl"] .modal-header {
flex-direction: row-reverse;
}
body[dir="rtl"] .form-group {
text-align: right;
}
body[dir="rtl"] .form-label {
text-align: right;
display: block;
}
body[dir="rtl"] .dashboard-header {
flex-direction: row-reverse;
}
body[dir="rtl"] .user-info {
flex-direction: row-reverse;
}
body[dir="rtl"] .modal-footer {
flex-direction: row-reverse;
}
</style>
</head>
<body x-data="app()" x-init="checkAuth()" :dir="lang === 'ar' ? 'rtl' : 'ltr'">
<div class="app-container">
<!-- Auth View -->
<template x-if="!isLoggedIn">
<div class="auth-wrapper">
<div class="auth-card">
<div class="brand-header">
<div class="brand-logo" id="main-brand-logo" style="display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 0.75rem;">
<img src="/logo.svg" alt="Nabeh Logo" style="width: 48px; height: 48px; border-radius: 12px; box-shadow: 0 0 15px rgba(6, 182, 212, 0.4);">
<span style="font-family: 'Outfit', sans-serif; font-size: 2.2rem; font-weight: 800; background: linear-gradient(135deg, #fff 30%, var(--primary-accent) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -0.05em;">Nabeh</span>
</div>
<div class="brand-subtitle">WhatsApp Hub & Marketing Automation</div>
</div>
<!-- Alert Banners -->
<template x-if="authError">
<div class="banner banner-danger" id="auth-error-banner">
<span x-text="authError"></span>
</div>
</template>
<template x-if="authSuccess">
<div class="banner banner-success" id="auth-success-banner">
<span x-text="authSuccess"></span>
</div>
</template>
<!-- Tabs Header -->
<div class="tabs-header">
<button class="tab-btn" :class="{ 'active': authTab === 'login' }" @click="authTab = 'login'; authError = ''; authSuccess = ''" id="tab-login-btn">Login</button>
<button class="tab-btn" :class="{ 'active': authTab === 'register' }" @click="authTab = 'register'; authError = ''; authSuccess = ''" id="tab-register-btn">Register</button>
</div>
<!-- Login Form -->
<form x-show="authTab === 'login'" @submit.prevent="login()" id="login-form">
<div class="form-group">
<label class="form-label">Email Address</label>
<input type="email" class="form-input" x-model="loginEmail" required placeholder="admin@company.com" id="login-email-input">
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" class="form-input" x-model="loginPassword" required placeholder="••••••••" id="login-password-input">
</div>
<button type="submit" class="btn btn-primary" :disabled="actionLoading" id="login-submit-btn">
<span x-show="!actionLoading">Sign In</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</form>
<!-- Register Form -->
<form x-show="authTab === 'register'" @submit.prevent="register()" id="register-form">
<div class="form-group">
<label class="form-label">Company Name</label>
<input type="text" class="form-input" x-model="regCompanyName" required placeholder="Acme Corporation" id="register-company-input">
</div>
<div class="form-group">
<label class="form-label">Administrator Name</label>
<input type="text" class="form-input" x-model="regUserName" required placeholder="John Doe" id="register-name-input">
</div>
<div class="form-group">
<label class="form-label">Email Address</label>
<input type="email" class="form-input" x-model="regEmail" required placeholder="admin@company.com" id="register-email-input">
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" class="form-input" x-model="regPassword" required placeholder="••••••••" id="register-password-input">
</div>
<button type="submit" class="btn btn-primary" :disabled="actionLoading" id="register-submit-btn">
<span x-show="!actionLoading">Create Account</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</form>
</div>
</div>
</template>
<!-- Main Dashboard View -->
<template x-if="isLoggedIn">
<div>
<div class="dashboard-header" style="display: flex; align-items: center; justify-content: space-between;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div style="display: flex; align-items: center; gap: 16px;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<img src="/logo.svg" alt="Nabeh Logo" style="width: 52px; height: 52px; border-radius: 12px; box-shadow: 0 0 15px rgba(6, 182, 212, 0.35); border: 1px solid rgba(255, 255, 255, 0.08);">
<div :style="lang === 'ar' ? 'text-align: right;' : ''">
<h1 style="font-size: 1.8rem; margin: 0 0 0.15rem 0;" x-text="lang === 'ar' ? 'لوحة تحكم نبيه' : 'Nabeh Dashboard'"></h1>
<p class="text-muted" style="font-size: 0.9rem; margin: 0;">
<span x-text="lang === 'ar' ? 'متصل بالنظام باسم:' : 'Connected to system:'"></span>
<span class="font-semibold" x-text="user.name"></span>
</p>
</div>
</div>
<div class="user-info" style="display: flex; align-items: center; gap: 12px;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<button @click="toggleLang()" class="btn btn-secondary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" id="lang-toggle-btn">
<span x-text="lang === 'ar' ? 'English 🇬🇧' : 'العربية 🇸🇦'"></span>
</button>
<div class="avatar" x-text="user.name.charAt(0).toUpperCase()"></div>
<button @click="logout()" class="btn btn-secondary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" id="logout-btn" x-text="lang === 'ar' ? 'تسجيل الخروج' : 'Log Out'"></button>
</div>
</div>
<div class="dashboard-layout">
<!-- Left Sidebar Nav -->
<div class="nav-menu">
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'whatsapp' }" @click="activeDashboardTab = 'whatsapp'" id="nav-whatsapp-btn">
<span>📱</span> <span x-text="lang === 'ar' ? 'اتصال الواتساب' : 'WhatsApp Connection'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'contacts' }" @click="activeDashboardTab = 'contacts'; fetchContacts(); fetchGroups()" id="nav-contacts-btn">
<span>👥</span> <span x-text="lang === 'ar' ? 'دليل جهات الاتصال' : 'Contacts Directory'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'templates' }" @click="activeDashboardTab = 'templates'; fetchTemplates()" id="nav-templates-btn">
<span>📝</span> <span x-text="lang === 'ar' ? 'قوالب الرسائل' : 'Message Templates'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'campaigns' }" @click="activeDashboardTab = 'campaigns'; fetchCampaigns()" id="nav-campaigns-btn">
<span>📣</span> <span x-text="lang === 'ar' ? 'الحملات التسويقية' : 'Marketing Campaigns'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'chatbot' }" @click="activeDashboardTab = 'chatbot'; fetchChatbotSettings()" id="nav-chatbot-btn">
<span>🤖</span> <span x-text="lang === 'ar' ? 'روبوت الذكاء الاصطناعي' : 'AI Chatbot Settings'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'integrations' }" @click="activeDashboardTab = 'integrations'; fetchEndpoints(); fetchSallaStatus(); fetchWooCommerceStatus()" id="nav-integrations-btn">
<span>🔌</span> <span x-text="lang === 'ar' ? 'الربط البرمجي والمنصات (Integrations)' : 'API & Platform Integrations'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'staff' }" @click="activeDashboardTab = 'staff'; fetchStaff(); fetchWhatsappSessions()" id="nav-staff-btn">
<span>👥</span> <span x-text="lang === 'ar' ? 'الموظفين والوكلاء' : 'CS Agents & Team'"></span>
</button>
</div>
<!-- Right Dashboard Panels -->
<div style="flex: 1;">
<!-- Global Dashboard Banner -->
<template x-if="dashboardSuccess">
<div class="banner banner-success" style="margin-bottom: 1.5rem;" id="dashboard-success-banner">
<span x-text="dashboardSuccess"></span>
<button @click="dashboardSuccess = ''" style="background: none; border: none; color: inherit; font-size: 1.2rem; cursor: pointer; margin-left: auto; margin-right: 0;" id="dashboard-success-close-btn">&times;</button>
</div>
</template>
<template x-if="dashboardError">
<div class="banner banner-danger" style="margin-bottom: 1.5rem;" id="dashboard-error-banner">
<span x-text="dashboardError"></span>
<button @click="dashboardError = ''" style="background: none; border: none; color: inherit; font-size: 1.2rem; cursor: pointer; margin-left: auto; margin-right: 0;" id="dashboard-error-close-btn">&times;</button>
</div>
</template>
<!-- Panel: WhatsApp Connection -->
<div class="panel" x-show="activeDashboardTab === 'whatsapp'" id="panel-whatsapp">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h2 style="font-size: 1.4rem; margin: 0;" x-text="lang === 'ar' ? 'إدارة قنوات اتصال واتساب' : 'WhatsApp Session Management'"></h2>
<div style="display: flex; gap: 0.5rem; align-items: center;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<input type="text" x-model="newSessionName" :placeholder="lang === 'ar' ? 'اسم الرقم (مثال: الدعم)' : 'Session Name (e.g. Sales)'" class="form-input" style="max-width: 200px; padding: 0.5rem 0.8rem; font-size: 0.85rem;" id="new-session-name-input">
<button @click="createWhatsappSession()" class="btn btn-primary" style="padding: 0.5rem 1rem; font-size: 0.85rem;" id="btn-create-session" :disabled="actionLoading">
<span x-text="lang === 'ar' ? '+ إضافة خط جديد' : '+ Add New Line'"></span>
</button>
</div>
</div>
<p class="text-muted" style="margin-bottom: 1.5rem; font-size: 0.9rem;" x-text="lang === 'ar' ? 'قم بإضافة وإدارة أرقام واتساب متعددة لشركتك طبقاً لباقة اشتراكك. يمكنك ربط كل موظف بخط محدد لمتابعة المحادثات.' : 'Add and manage multiple WhatsApp connections. You can assign customer service agents to specific phone lines based on your plan features.'"></p>
<!-- Sessions Grid Layout -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
<template x-for="session in whatsappSessions" :key="session.id">
<div class="status-box" style="margin: 0; padding: 1.5rem; display: flex; flex-direction: column; justify-content: space-between; height: 100%; border: 1px solid var(--card-border); background: var(--card-bg); border-radius: 16px;">
<div>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div>
<h3 style="font-size: 1.1rem; font-weight: 700;" x-text="session.name"></h3>
<span style="font-family: monospace; font-size: 0.8rem; color: var(--text-muted);" x-text="session.session_key"></span>
</div>
<div class="status-badge" :class="{
'badge-disconnected': session.status === 'disconnected',
'badge-connecting': session.status === 'connecting',
'badge-waiting_qr': session.status === 'waiting_qr',
'badge-connected': session.status === 'connected'
}" style="margin: 0; font-size: 0.75rem; padding: 0.2rem 0.5rem;">
<span x-text="session.status"></span>
</div>
</div>
<p style="font-size: 0.9rem; margin-bottom: 1.5rem; color: var(--text-muted);" :style="lang === 'ar' ? 'text-align: right;' : 'text-align: left;'">
<template x-if="session.phone">
<span>📞 <strong x-text="session.phone" style="color: var(--text-main);"></strong></span>
</template>
<template x-if="!session.phone">
<span x-text="lang === 'ar' ? 'الخط غير مرتبط برقم هاتف بعد.' : 'No phone linked yet. Scan QR code.'"></span>
</template>
</p>
</div>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end; flex-wrap: wrap;">
<!-- Connect/Scan QR Action -->
<template x-if="session.status === 'disconnected' || session.status === 'waiting_qr' || session.status === 'connecting'">
<button @click="connectWhatsapp(session.id)" class="btn btn-primary" style="padding: 0.4rem 0.8rem; font-size: 0.8rem;" :disabled="actionLoading">
<span x-text="lang === 'ar' ? 'ربط / مسح رمز QR' : 'Link / Scan QR'"></span>
</button>
</template>
<!-- Disconnect Action -->
<template x-if="session.status === 'connected'">
<button @click="disconnectWhatsapp(session.id)" class="btn btn-glass" style="padding: 0.4rem 0.8rem; font-size: 0.8rem; color: var(--warning);" :disabled="actionLoading">
<span x-text="lang === 'ar' ? 'قطع الاتصال' : 'Disconnect'"></span>
</button>
</template>
<!-- Delete Action -->
<button @click="deleteWhatsappSession(session.id)" class="btn btn-danger" style="padding: 0.4rem 0.8rem; font-size: 0.8rem;" :disabled="actionLoading">
<span x-text="lang === 'ar' ? 'حذف' : 'Delete'"></span>
</button>
</div>
</div>
</template>
<template x-if="whatsappSessions.length === 0">
<div style="grid-column: 1 / -1; text-align: center; padding: 3rem; color: var(--text-muted);" x-text="lang === 'ar' ? 'لا توجد قنوات واتساب نشطة. أضف خطاً جديداً أعلاه للبدء.' : 'No active WhatsApp channels configured. Create a session above to get started.'"></div>
</template>
</div>
<div class="grid-two" style="align-items: start;">
<!-- QR Display Card (Active selected session) -->
<div class="card" style="margin: 0;" x-show="whatsappSession && (whatsappSession.status === 'connecting' || whatsappSession.status === 'waiting_qr' || whatsappSession.status === 'connected')">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h3 style="font-size: 1.1rem; font-weight: 700;" x-text="(lang === 'ar' ? 'ربط الخط: ' : 'Linking Line: ') + (whatsappSession?.name || '')"></h3>
<button class="modal-close" style="font-size: 1.25rem;" @click="whatsappSession = null">&times;</button>
</div>
<div class="flex-center flex-column" style="background: rgba(10, 11, 20, 0.2); border: 1px solid var(--card-border); border-radius: 12px; padding: 2rem;">
<template x-if="whatsappSession?.status === 'connecting'">
<div class="text-center">
<div class="spinner spinner-large" style="margin-bottom: 1rem;"></div>
<p class="font-semibold" x-text="lang === 'ar' ? 'جاري الاتصال بالبوابة...' : 'Connecting to Gateway...'"></p>
<p class="text-muted" style="font-size: 0.8rem; margin-top: 0.25rem;" x-text="lang === 'ar' ? 'يرجى الانتظار لحين جلب حالة الخط وطلب الرمز من البوابة.' : 'Preparing session and requesting QR code stream.'"></p>
</div>
</template>
<template x-if="whatsappSession?.status === 'waiting_qr'">
<div class="text-center">
<p class="font-semibold" x-text="lang === 'ar' ? 'امسح رمز الاستجابة السريعة (QR Code)' : 'Scan QR Code'"></p>
<p class="text-muted" style="font-size: 0.8rem; margin-top: 0.25rem; margin-bottom: 1rem;" x-text="lang === 'ar' ? 'افتح واتساب > الأجهزة المرتبطة > ربط جهاز' : 'Open WhatsApp > Linked Devices > Link a Device'"></p>
<div class="qr-wrapper" style="background: #ffffff; padding: 1rem; border-radius: 12px; display: inline-block;">
<div id="qrcode-canvas" x-init="$nextTick(() => renderQr())"></div>
</div>
<div style="font-size: 0.8rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-top: 1rem;" class="text-muted">
<div class="spinner" style="width: 14px; height: 14px; border-width: 2px;"></div>
<span x-text="lang === 'ar' ? 'بانتظار إتمام المصادقة من الهاتف...' : 'Waiting for connection handshake...'"></span>
</div>
</div>
</template>
<template x-if="whatsappSession?.status === 'connected'">
<div class="text-center" style="padding: 1.5rem 0;">
<div style="font-size: 3rem; color: var(--success); margin-bottom: 0.5rem; text-shadow: 0 0 20px rgba(16,185,129,0.3);"></div>
<p class="font-semibold" style="font-size: 1.1rem; color: var(--success);" x-text="lang === 'ar' ? 'الخط متصل بالكامل برقم الهاتف!' : 'Line fully linked and connected!'"></p>
<p class="text-muted" style="font-size: 0.85rem; margin-top: 0.25rem;" x-text="lang === 'ar' ? 'الرقم متصل ويمكنه الآن إرسال الحملات ورموز التحقق واستقبال الطلبات.' : 'This line is active and ready to deliver campaign broadcasts and verification OTPs.'"></p>
</div>
</template>
</div>
</div>
<!-- OTP Test Widget Card -->
<div class="card" style="margin: 0;">
<h3 style="font-size: 1.1rem; font-weight: 700; margin-bottom: 1.25rem; display: flex; align-items: center; gap: 0.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<span>🔑</span>
<span x-text="lang === 'ar' ? 'أداة اختبار إرسال رمز التحقق (OTP)' : 'OTP Deliverability Test Tool'"></span>
</h3>
<form @submit.prevent="sendOtpTest()">
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'خط الإرسال (WhatsApp Line)' : 'Sender Line (WhatsApp)'"></label>
<select x-model="otpSessionId" required>
<option value="" x-text="lang === 'ar' ? '-- اختر الخط --' : '-- Choose Line --'"></option>
<template x-for="session in whatsappSessions" :key="session.id">
<option :value="session.id" x-text="session.name + (session.phone ? ' (' + session.phone + ')' : ' - ' + session.status)"></option>
</template>
</select>
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'رقم الهاتف المستلم (مع رمز الدولة)' : 'Recipient Phone (with country code)'"></label>
<input type="text" x-model="otpPhone" class="form-input" required placeholder="966500000000" id="otp-test-recipient">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'نوع الرسالة' : 'OTP Type'"></label>
<select x-model="otpType">
<option value="image" x-text="lang === 'ar' ? 'صورة آمنة (Image CAPTCHA OTP)' : 'Secure Image (Anti-Bot OTP)'"></option>
<option value="text" x-text="lang === 'ar' ? 'رسالة نصية على الواتساب' : 'Text Message'"></option>
<option value="voice" x-text="lang === 'ar' ? 'رسالة صوتية (Voice Note OTP)' : 'Voice Note (Google TTS)'"></option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;" :disabled="actionLoading" id="btn-send-otp-test">
<span x-show="!actionLoading" x-text="lang === 'ar' ? 'إرسال رمز التحقق الآن' : 'Deliver OTP Code'"></span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</form>
<template x-if="otpStatusMsg">
<div class="banner" :class="otpErrorCode ? 'banner-danger' : 'banner-success'" style="margin-top: 1rem; font-size: 0.85rem;" id="otp-test-banner">
<span x-text="otpStatusMsg"></span>
</div>
</template>
</div>
</div>
</div>
<!-- Panel: Contacts Directory -->
<div class="panel" x-show="activeDashboardTab === 'contacts'" id="panel-contacts">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem;">
<h2 style="font-size: 1.4rem;">Contacts Directory</h2>
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="openAddContactModal()" id="btn-add-contact">+ Add Contact</button>
</div>
<!-- Bulk Action Bar -->
<div x-show="selectedContactIds.length > 0" class="bulk-action-bar" style="background: rgba(6, 182, 212, 0.1); border: 1px solid var(--primary-accent); padding: 1rem; border-radius: 10px; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; animation: fadeIn 0.3s ease-out;">
<span style="font-size: 0.9rem; color: var(--text-primary); font-weight: 600;">
Selected: <span x-text="selectedContactIds.length" style="color: var(--primary-accent);"></span> contact(s)
</span>
<div style="display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;">
<select class="form-input" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" x-model="bulkGroupId" @focus="fetchGroups()">
<option value="">-- Add to Existing Group --</option>
<template x-for="grp in groups" :key="grp.id">
<option :value="grp.id" x-text="grp.name"></option>
</template>
</select>
<span style="color: var(--text-secondary); font-size: 0.85rem;">or</span>
<input type="text" class="form-input" style="width: 180px; padding: 0.5rem 1rem; font-size: 0.85rem;" placeholder="New Group Name" x-model="bulkNewGroupName">
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="applyBulkGroup()">Apply Grouping</button>
</div>
</div>
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 40px; text-align: center;">
<input type="checkbox" @change="selectedContactIds = $event.target.checked ? contacts.map(c => c.id) : []" :checked="contacts.length > 0 && selectedContactIds.length === contacts.length" style="cursor: pointer; width: 16px; height: 16px;">
</th>
<th>Name</th>
<th>Phone</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<template x-for="contact in contacts" :key="contact.id">
<tr>
<td style="text-align: center;">
<input type="checkbox" :value="contact.id" x-model="selectedContactIds" style="cursor: pointer; width: 16px; height: 16px;">
</td>
<td class="font-semibold" x-text="contact.name"></td>
<td x-text="contact.phone || 'N/A'"></td>
<td x-text="contact.email || 'N/A'"></td>
<td>
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: var(--success-accent); margin-right: 0.25rem;"></span>
<span>Active</span>
</td>
</tr>
</template>
</tbody>
</table>
<template x-if="contacts.length === 0">
<div class="empty-state">No contacts added yet.</div>
</template>
</div>
</div>
<!-- Panel: Message Templates -->
<div class="panel" x-show="activeDashboardTab === 'templates'" id="panel-templates">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem;">
<h2 style="font-size: 1.4rem;">Templates</h2>
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="openNewTemplateModal()" id="btn-add-template">+ New Template</button>
</div>
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th>Template Name</th>
<th>Category</th>
<th>Language</th>
<th>Variables</th>
</tr>
</thead>
<tbody>
<template x-for="tpl in templates" :key="tpl.id">
<tr>
<td class="font-semibold" x-text="tpl.name"></td>
<td x-text="tpl.category || 'Utility'"></td>
<td x-text="tpl.language || 'en'"></td>
<td x-text="tpl.variables || 'None'"></td>
</tr>
</template>
</tbody>
</table>
<template x-if="templates.length === 0">
<div class="empty-state">No templates created yet.</div>
</template>
</div>
</div>
<!-- Panel: Campaigns -->
<div class="panel" x-show="activeDashboardTab === 'campaigns'" id="panel-campaigns">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem;">
<h2 style="font-size: 1.4rem;">Campaigns</h2>
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="openLaunchCampaignModal()" id="btn-add-campaign">+ Launch Campaign</button>
</div>
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th>Campaign Name</th>
<th>Template</th>
<th>Status</th>
<th>Sent Count</th>
</tr>
</thead>
<tbody>
<template x-for="cmp in campaigns" :key="cmp.id">
<tr>
<td class="font-semibold" x-text="cmp.name"></td>
<td x-text="cmp.template_name || 'N/A'"></td>
<td>
<span class="status-badge" :class="cmp.status === 'completed' ? 'badge-connected' : 'badge-connecting'" style="margin: 0; padding: 0.2rem 0.5rem; font-size: 0.75rem;" x-text="cmp.status"></span>
</td>
<td x-text="cmp.sent_count || 0"></td>
</tr>
</template>
</tbody>
</table>
<template x-if="campaigns.length === 0">
<div class="empty-state">No campaigns created yet.</div>
</template>
</div>
</div>
<!-- Panel: AI Chatbot Settings -->
<div class="panel" x-show="activeDashboardTab === 'chatbot'" id="panel-chatbot">
<h2 style="font-size: 1.4rem; margin-bottom: 1.5rem;">AI Chatbot & Auto-Reply Settings</h2>
<form @submit.prevent="saveChatbotSettings()" id="chatbot-form">
<div class="form-group">
<label class="form-label">Enable Chatbot</label>
<select class="form-input" x-model="chatbotSettings.is_active" id="chatbot-active-select">
<option value="1">Active</option>
<option value="0">Disabled</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Auto-Reply Type</label>
<select class="form-input" x-model="chatbotSettings.trigger_type" id="chatbot-trigger-type-select">
<option value="keyword">Keyword-Match (Static reply)</option>
<option value="gemini_ai">Gemini AI (Dynamic conversational responder)</option>
</select>
</div>
<div class="form-group" x-show="chatbotSettings.trigger_type === 'keyword'" id="chatbot-keyword-group">
<label class="form-label">Trigger Keywords (Comma separated)</label>
<input type="text" class="form-input" x-model="chatbotSettings.keyword" placeholder="hello, price, discount, support" id="chatbot-keyword-input">
</div>
<div class="form-group" x-show="chatbotSettings.trigger_type === 'gemini_ai'" id="chatbot-api-key-group">
<label class="form-label">Google Gemini API Key</label>
<input type="password" class="form-input" x-model="chatbotSettings.gemini_api_key" placeholder="••••••••" id="chatbot-api-key-input">
<span style="font-size: 0.75rem; color: var(--text-secondary); display: block; margin-top: 0.25rem;">
Leave empty to use the system default API key configured in .env.
</span>
</div>
<div class="form-group" id="chatbot-prompt-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; gap: 0.5rem;">
<label class="form-label" x-text="chatbotSettings.trigger_type === 'gemini_ai' ? 'System Instruction Prompt' : 'Predefined Auto-Reply Message'" style="margin-bottom: 0;"></label>
<div x-show="chatbotSettings.trigger_type === 'gemini_ai'" style="display: flex; gap: 0.35rem; flex-wrap: wrap; align-items: center;">
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('intaleq')">قالب خدمة عملاء انطلق (سوري)</button>
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('nabeh')">قالب تطبيق نبيه (سوري)</button>
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('store')">قالب متجر إلكتروني</button>
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('general')">قالب عام (إنجليزي)</button>
<!-- Voice Recording Button -->
<button type="button" class="btn" :class="isRecording ? 'btn-danger' : 'btn-secondary'" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto; display: flex; align-items: center; gap: 0.25rem;" @click="toggleVoiceRecording()">
<svg x-show="!isRecording" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
<span x-show="isRecording" class="recording-pulse" style="display: inline-block; width: 8px; height: 8px; background-color: white; border-radius: 50%;"></span>
<span x-text="isRecording ? 'إيقاف التسجيل (' + formatRecordTime(recordingTime) + ')' : '🎤 تسجيل الإرشادات'"></span>
</button>
</div>
</div>
<textarea class="form-input" x-model="chatbotSettings.ai_prompt" rows="7" required :placeholder="chatbotSettings.trigger_type === 'gemini_ai' ? 'أدخل تعليمات الذكاء الاصطناعي هنا...' : 'شكراً لتواصلك معنا!'" id="chatbot-prompt-input" :disabled="generatingFromVoice"></textarea>
<!-- Voice Processing Loading Status -->
<div x-show="generatingFromVoice" style="margin-top: 0.5rem; display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; color: var(--primary-accent);" dir="rtl">
<span class="spinner" style="border-top-color: var(--primary-accent); display: inline-block;"></span>
<span>جاري صياغة التوجيهات من صوتك باستخدام الذكاء الاصطناعي... يرجى الانتظار</span>
</div>
<!-- Hints & Guidelines for Merchant -->
<div x-show="chatbotSettings.trigger_type === 'gemini_ai'" style="margin-top: 0.75rem; padding: 0.75rem; background: rgba(59, 130, 246, 0.05); border: 1px dashed rgba(59, 130, 246, 0.3); border-radius: 8px; font-size: 0.8rem; color: var(--text-secondary);" dir="rtl">
<strong style="color: var(--text-primary); display: block; margin-bottom: 0.35rem; display: flex; align-items: center; gap: 0.35rem;">
💡 نصائح وتوجيهات لكتابة تعليمات ممتازة:
</strong>
<ul style="list-style-type: disc; margin-right: 1.25rem; padding-left: 0; line-height: 1.5; margin-bottom: 0;">
<li><strong>الهوية والاسم:</strong> حدد اسم الروبوت بوضوح (مثال: "أنا سارة من فريق تطبيق نبيه").</li>
<li><strong>اللهجة والأسلوب:</strong> اطلب من الذكاء الاصطناعي الرد بلهجة معينة (مثال: اللهجة السورية أو الفصحى المبسطة).</li>
<li><strong>البيانات الأساسية:</strong> اكتب ساعات عمل المتجر، طرق الدفع والتوصيل، وسياسة الاستبدال لكي يجيب الروبوت بدقة.</li>
<li><strong>التعليمات اللغوية:</strong> قمنا بتضمين ميزة مطابقة اللغة تلقائياً (الرد بالإنجليزية على الرسائل الإنجليزية، وبالعربية على العربية).</li>
</ul>
</div>
</div>
<button type="submit" class="btn btn-primary" :disabled="actionLoading" id="chatbot-save-btn">
<span x-show="!actionLoading">Save Settings</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</form>
</div>
<!-- Panel: API Integrations -->
<div class="panel" x-show="activeDashboardTab === 'integrations'" id="panel-integrations">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h2 style="font-size: 1.4rem; margin: 0;" x-text="lang === 'ar' ? 'ربط تطبيق نبيه بمشروعك (API Integrations)' : 'API Endpoints Integration'"></h2>
<button class="btn btn-primary" style="width: auto;" @click="endpointForm = { id: null, name: '', endpoint_url: '', action_type: 'verify_payment', description: '', api_key: '' }; showAddEndpointModal = true" id="add-endpoint-btn">
<span x-text="lang === 'ar' ? '+ إضافة ربط برمجي' : '+ Add API Integration'"></span>
</button>
</div>
<p class="text-muted" style="margin-bottom: 1.5rem; font-size: 0.9rem;" x-text="lang === 'ar' ? 'قم بتهيئة واجهات برمجة التطبيقات الخارجية (Web APIs) للربط البرمجي بمشروعك. يمكن للروبوت جلب الملفات التعريفية للمستخدمين أو التحقق من تفاصيل الدفع ديناميكيًا.' : 'Configure external web APIs for multi-tenant integrations. The chatbot can fetch user profiles or verify payment details dynamically.'">
</p>
<!-- Salla Integration Section -->
<div style="background: rgba(0, 178, 137, 0.05); border: 1px solid rgba(0, 178, 137, 0.15); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem; box-shadow: 0 4px 20px rgba(0, 178, 137, 0.05);">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div style="display: flex; align-items: center; gap: 1rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse; text-align: right;' : ''">
<div style="background: #00b289; width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 15px rgba(0, 178, 137, 0.4);">
<span style="font-size: 1.5rem;">🛍️</span>
</div>
<div>
<h3 style="font-size: 1.2rem; margin: 0; display: flex; align-items: center; gap: 0.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<span x-text="lang === 'ar' ? 'ربط متجر سلة (Salla)' : 'Salla Store Integration'"></span>
<template x-if="sallaStatus && sallaStatus.connected">
<span class="status-badge badge-connected" style="margin: 0; padding: 0.15rem 0.5rem; font-size: 0.7rem;" x-text="lang === 'ar' ? 'متصل' : 'Connected'"></span>
</template>
<template x-if="!sallaStatus || !sallaStatus.connected">
<span class="status-badge badge-disconnected" style="margin: 0; padding: 0.15rem 0.5rem; font-size: 0.7rem;" x-text="lang === 'ar' ? 'غير متصل' : 'Disconnected'"></span>
</template>
</h3>
<p class="text-muted" style="margin: 0.25rem 0 0 0; font-size: 0.85rem;" x-text="lang === 'ar' ? 'قم بربط متجر سلة الخاص بك لتفعيل الاستعلام التلقائي عن الطلبات وتتبع الشحنات وإرسال تنبيهات التحديثات للعملاء عبر الواتساب.' : 'Connect your Salla store to enable automatic order tracking and send customer updates via WhatsApp using Gemini AI.'"></p>
</div>
</div>
<div style="display: flex; gap: 0.75rem; align-items: center;">
<template x-if="sallaLoading">
<span class="spinner"></span>
</template>
<template x-if="!sallaLoading && (!sallaStatus || !sallaStatus.connected)">
<button @click="connectSalla()" class="btn" style="background: linear-gradient(135deg, #00b289 0%, #009673 100%); color: #fff; width: auto; font-size: 0.9rem; padding: 0.6rem 1.2rem; box-shadow: 0 4px 12px rgba(0, 178, 137, 0.3);" id="connect-salla-btn">
<span x-text="lang === 'ar' ? 'ربط المتجر الآن' : 'Connect Store'"></span>
</button>
</template>
<template x-if="!sallaLoading && sallaStatus && sallaStatus.connected">
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div style="text-align: right;" :style="lang === 'ar' ? 'text-align: right;' : 'text-align: left;'">
<span style="font-size: 0.8rem; color: var(--text-secondary);" x-text="lang === 'ar' ? 'المتجر المرتبط:' : 'Connected Store:'"></span>
<strong style="display: block; font-size: 0.95rem; color: #fff;" x-text="sallaStatus.store_name"></strong>
</div>
<button @click="disconnectSalla()" class="btn btn-danger" style="width: auto; font-size: 0.9rem; padding: 0.6rem 1.2rem;" id="disconnect-salla-btn">
<span x-text="lang === 'ar' ? 'إلغاء الربط' : 'Disconnect'"></span>
</button>
</div>
</template>
</div>
</div>
</div>
<!-- WooCommerce Integration Section -->
<div style="background: rgba(99, 102, 241, 0.05); border: 1px solid rgba(99, 102, 241, 0.15); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem; box-shadow: 0 4px 20px rgba(99, 102, 241, 0.05);">
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div style="display: flex; align-items: center; gap: 1rem; flex: 1;" :style="lang === 'ar' ? 'flex-direction: row-reverse; text-align: right;' : ''">
<div style="background: var(--primary); width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);">
<span style="font-size: 1.5rem;">⚙️</span>
</div>
<div>
<h3 style="font-size: 1.2rem; margin: 0; display: flex; align-items: center; gap: 0.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<span x-text="lang === 'ar' ? 'ربط متجر ووكومرس (WooCommerce)' : 'WooCommerce Store Integration'"></span>
<template x-if="woocommerceStatus && woocommerceStatus.connected">
<span class="status-badge badge-connected" style="margin: 0; padding: 0.15rem 0.5rem; font-size: 0.7rem;" x-text="lang === 'ar' ? 'متصل' : 'Connected'"></span>
</template>
<template x-if="!woocommerceStatus || !woocommerceStatus.connected">
<span class="status-badge badge-disconnected" style="margin: 0; padding: 0.15rem 0.5rem; font-size: 0.7rem;" x-text="lang === 'ar' ? 'غير متصل' : 'Disconnected'"></span>
</template>
</h3>
<p class="text-muted" style="margin: 0.25rem 0 0 0; font-size: 0.85rem;" x-text="lang === 'ar' ? 'قم بربط متجر WooCommerce لإرسال إشعارات تغيير حالة الطلبات للعملاء تلقائيًا عبر الواتساب.' : 'Link your WooCommerce store to trigger automated customer notifications via WhatsApp on order events.'"></p>
</div>
</div>
<div style="display: flex; gap: 0.75rem; align-items: center;">
<template x-if="woocommerceLoading">
<span class="spinner"></span>
</template>
<template x-if="!woocommerceLoading && woocommerceStatus && woocommerceStatus.connected">
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<div style="text-align: right;" :style="lang === 'ar' ? 'text-align: right;' : 'text-align: left;'">
<span style="font-size: 0.8rem; color: var(--text-secondary);" x-text="lang === 'ar' ? 'المتجر المرتبط:' : 'Connected URL:'"></span>
<strong style="display: block; font-size: 0.95rem; color: #fff;" x-text="woocommerceStatus.store_url"></strong>
</div>
<button @click="disconnectWooCommerce()" class="btn btn-danger" style="width: auto; font-size: 0.9rem; padding: 0.6rem 1.2rem;" id="disconnect-woo-btn">
<span x-text="lang === 'ar' ? 'إلغاء الربط' : 'Disconnect'"></span>
</button>
</div>
</template>
</div>
</div>
<!-- WooCommerce Connect Form -->
<template x-if="!woocommerceStatus || !woocommerceStatus.connected">
<form @submit.prevent="connectWooCommerce()" style="margin-top: 1.5rem; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 1.5rem;">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.25rem;">
<div class="form-group" style="margin: 0;">
<label class="form-label" x-text="lang === 'ar' ? 'رابط المتجر (Store URL)' : 'Store URL'"></label>
<input type="url" class="form-input" x-model="wooForm.store_url" required placeholder="https://my-store.com">
</div>
<div class="form-group" style="margin: 0;">
<label class="form-label" x-text="lang === 'ar' ? 'Consumer Key (ck_...)' : 'Consumer Key (ck_...)'"></label>
<input type="text" class="form-input" x-model="wooForm.consumer_key" required placeholder="ck_...">
</div>
<div class="form-group" style="margin: 0;">
<label class="form-label" x-text="lang === 'ar' ? 'Consumer Secret (cs_...)' : 'Consumer Secret (cs_...)'"></label>
<input type="password" class="form-input" x-model="wooForm.consumer_secret" required placeholder="cs_...">
</div>
<div class="form-group" style="margin: 0;">
<label class="form-label" x-text="lang === 'ar' ? 'Webhook Secret (اختياري)' : 'Webhook Secret (Optional)'"></label>
<input type="text" class="form-input" x-model="wooForm.webhook_secret" placeholder="Secret code to secure webhook signature">
</div>
</div>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="woocommerceLoading" id="connect-woo-btn">
<span x-text="lang === 'ar' ? 'ربط ووكومرس وتفعيل الاشعارات' : 'Link WooCommerce Store'"></span>
</button>
</form>
</template>
<!-- WooCommerce Connected Details -->
<template x-if="woocommerceStatus && woocommerceStatus.connected">
<div style="margin-top: 1rem; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 1rem; font-size: 0.85rem;">
<div style="background: rgba(255,255,255,0.02); border: 1px dashed var(--card-border-hover); border-radius: 8px; padding: 1rem; font-family: monospace;">
<p style="font-weight: 600; margin-bottom: 0.25rem; color: var(--text-main);" x-text="lang === 'ar' ? 'رابط الـ Webhook الخاص بمتجرك:' : 'Delivery URL for WooCommerce Webhook:'"></p>
<p style="word-break: break-all; color: var(--secondary);" x-text="woocommerceStatus.webhook_url"></p>
<p style="margin-top: 0.5rem; color: var(--text-muted); font-size: 0.8rem;" x-text="lang === 'ar' ? 'قم بإنشاء Webhooks في ووكومرس للأحداث (Order Created & Order Updated) والصق هذا الرابط هناك.' : 'Create Order Created and Order Updated webhooks in WooCommerce settings using this delivery URL.'"></p>
</div>
</div>
</template>
</div>
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th x-text="lang === 'ar' ? 'اسم الربط' : 'Integration Name'"></th>
<th x-text="lang === 'ar' ? 'نوع الإجراء' : 'Action Type'"></th>
<th x-text="lang === 'ar' ? 'رابط نقطة النهاية (Endpoint URL)' : 'Endpoint URL'"></th>
<th x-text="lang === 'ar' ? 'الوصف' : 'Description'"></th>
<th style="width: 150px; text-align: center;" x-text="lang === 'ar' ? 'الخيارات' : 'Actions'"></th>
</tr>
</thead>
<tbody>
<template x-for="endpoint in endpoints" :key="endpoint.id">
<tr>
<td class="font-semibold" x-text="endpoint.name"></td>
<td>
<span class="status-badge" :class="endpoint.action_type === 'verify_payment' ? 'badge-waiting_qr' : 'badge-connecting'" style="margin: 0; padding: 0.2rem 0.5rem; font-size: 0.75rem;" x-text="endpoint.action_type === 'verify_payment' ? (lang === 'ar' ? 'تأكيد الدفع' : 'Verify Payment') : (endpoint.action_type === 'fetch_user_info' ? (lang === 'ar' ? 'جلب بيانات العميل' : 'Fetch User Info') : endpoint.action_type)"></span>
</td>
<td style="font-family: monospace; font-size: 0.85rem;" x-text="endpoint.endpoint_url"></td>
<td x-text="endpoint.description || (lang === 'ar' ? 'لا يوجد وصف.' : 'No description provided.')"></td>
<td style="text-align: center; display: flex; gap: 0.5rem; justify-content: center; align-items: center;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<button class="btn btn-secondary" style="width: auto; padding: 0.4rem 0.8rem; font-size: 0.8rem;" @click="editEndpoint(endpoint)" :id="'edit-endpoint-btn-' + endpoint.id" x-text="lang === 'ar' ? 'تعديل' : 'Edit'"></button>
<button class="btn btn-danger" style="width: auto; padding: 0.4rem 0.8rem; font-size: 0.8rem;" @click="deleteEndpoint(endpoint.id)" :id="'delete-endpoint-btn-' + endpoint.id" x-text="lang === 'ar' ? 'حذف' : 'Delete'"></button>
</td>
</tr>
</template>
</tbody>
</table>
<template x-if="endpoints.length === 0">
<div class="empty-state" id="empty-endpoints-state" x-text="lang === 'ar' ? 'لم يتم تكوين نقاط نهاية واجهة برمجة تطبيقات (API Endpoints) بعد.' : 'No API endpoints configured yet. Connect Intaleq or Salla integrations.'"></div>
</template>
</div>
</div>
<!-- Panel: Customer Service Team & Staff -->
<div class="panel" x-show="activeDashboardTab === 'staff'" id="panel-staff">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h2 style="font-size: 1.4rem; margin: 0;" x-text="lang === 'ar' ? 'فريق خدمة العملاء والوكلاء (Staff)' : 'Customer Service Agents & Staff'"></h2>
<button class="btn btn-primary" style="width: auto;" @click="staffForm = { name: '', email: '', password: '', whatsapp_session_id: '' }; showAddStaffModal = true" id="add-agent-btn">
<span x-text="lang === 'ar' ? '+ إضافة موظف جديد' : '+ Add CS Agent'"></span>
</button>
</div>
<p class="text-muted" style="margin-bottom: 1.5rem; font-size: 0.9rem;" x-text="lang === 'ar' ? 'قم بإضافة موظفي خدمة العملاء وتعيين كل منهم لرقم واتساب محدد. يستطيع كل موظف قراءة والرد على رسائل الرقم المخصص له فقط.' : 'Add agents to your team and bind them to specific WhatsApp lines. Each staff member can only view and manage chats for their assigned numbers.'"></p>
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th x-text="lang === 'ar' ? 'الاسم' : 'Agent Name'"></th>
<th x-text="lang === 'ar' ? 'البريد الإلكتروني' : 'Email Address'"></th>
<th x-text="lang === 'ar' ? 'رقم الواتساب المعين' : 'Assigned WhatsApp Line'"></th>
<th style="width: 250px; text-align: center;" x-text="lang === 'ar' ? 'خيارات وتغيير التعيين' : 'Management & Assign'"></th>
</tr>
</thead>
<tbody>
<template x-for="agent in staff" :key="agent.id">
<tr>
<td class="font-semibold" x-text="agent.name"></td>
<td style="font-family: monospace; font-size: 0.85rem;" x-text="agent.email"></td>
<td>
<span class="badge badge-primary" x-show="agent.whatsapp_session_id" x-text="agent.session_name + (agent.session_phone ? ' (' + agent.session_phone + ')' : '')"></span>
<span class="badge badge-danger" x-show="!agent.whatsapp_session_id" x-text="lang === 'ar' ? 'غير معين' : 'Unassigned'"></span>
</td>
<td style="text-align: center; display: flex; gap: 0.5rem; justify-content: center; align-items: center;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<!-- Assign Session Select -->
<select style="font-size: 0.8rem; padding: 0.3rem 0.5rem; width: auto;" :value="agent.whatsapp_session_id || ''" @change="assignSessionToStaff(agent.id, $event.target.value)">
<option value="" x-text="lang === 'ar' ? '-- بدون تعيين --' : '-- Unassigned --'"></option>
<template x-for="session in whatsappSessions" :key="session.id">
<option :value="session.id" x-text="session.name + (session.phone ? ' (' + session.phone + ')' : '')"></option>
</template>
</select>
<button class="btn btn-danger" style="width: auto; padding: 0.3rem 0.6rem; font-size: 0.8rem; margin: 0;" @click="deleteStaff(agent.id)">
<span x-text="lang === 'ar' ? 'حذف' : 'Delete'"></span>
</button>
</td>
</tr>
</template>
</tbody>
</table>
<template x-if="staff.length === 0">
<div class="empty-state" id="empty-staff-state" x-text="lang === 'ar' ? 'لم تقم بإضافة موظفي خدمة عملاء بعد.' : 'No customer service agents added yet.'"></div>
</template>
</div>
</div>
</div>
</div>
</div>
<!-- Modal: Add Contact -->
<div class="modal-overlay" x-show="showAddContactModal" id="modal-add-contact" style="display: none;">
<div class="modal-card">
<div class="modal-header">
<h3 class="modal-title">Add New Contact</h3>
<button class="modal-close" @click="showAddContactModal = false">&times;</button>
</div>
<form @submit.prevent="submitAddContact()" id="form-add-contact">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Name</label>
<input type="text" class="form-input" x-model="contactName" required placeholder="John Doe" id="add-contact-name">
</div>
<div class="form-group">
<label class="form-label">Phone Number (with Country Code)</label>
<input type="text" class="form-input" x-model="contactPhone" required placeholder="966500000000" id="add-contact-phone">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" class="form-input" x-model="contactEmail" placeholder="john@example.com" id="add-contact-email">
</div>
<div class="form-group">
<label class="form-label">Notes</label>
<textarea class="form-input" x-model="contactNotes" placeholder="Additional details..." id="add-contact-notes"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" style="width: auto;" @click="showAddContactModal = false">Cancel</button>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="actionLoading">
<span x-show="!actionLoading">Create Contact</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</div>
</form>
</div>
</div>
<!-- Modal: Add Staff / Agent -->
<div class="modal-overlay" x-show="showAddStaffModal" id="modal-add-staff" style="display: none;">
<div class="modal-card">
<div class="modal-header" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h3 class="modal-title" x-text="lang === 'ar' ? 'إضافة موظف خدمة عملاء جديد' : 'Add Customer Service Agent'"></h3>
<button class="modal-close" @click="showAddStaffModal = false">&times;</button>
</div>
<form @submit.prevent="submitAddStaff()" id="form-add-staff">
<div class="modal-body">
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'اسم الموظف' : 'Agent Name'"></label>
<input type="text" class="form-input" x-model="staffForm.name" required placeholder="Ali Ahmed">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'البريد الإلكتروني' : 'Email Address'"></label>
<input type="email" class="form-input" x-model="staffForm.email" required placeholder="ali@example.com">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'كلمة المرور' : 'Password'"></label>
<input type="password" class="form-input" x-model="staffForm.password" required placeholder="******">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'تعيين خط واتساب' : 'Assign WhatsApp Line'"></label>
<select x-model="staffForm.whatsapp_session_id">
<option value="" x-text="lang === 'ar' ? '-- بدون تعيين --' : '-- Leave Unassigned --'"></option>
<template x-for="session in whatsappSessions" :key="session.id">
<option :value="session.id" x-text="session.name + (session.phone ? ' (' + session.phone + ')' : '')"></option>
</template>
</select>
</div>
</div>
<div class="modal-footer" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<button type="button" class="btn btn-secondary" style="width: auto;" @click="showAddStaffModal = false" x-text="lang === 'ar' ? 'إلغاء' : 'Cancel'"></button>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="actionLoading">
<span x-show="!actionLoading" x-text="lang === 'ar' ? 'إضافة الموظف' : 'Add Agent'"></span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</div>
</form>
</div>
</div>
<!-- Modal: Add/Edit Endpoint Integration -->
<div class="modal-overlay" x-show="showAddEndpointModal" id="modal-add-endpoint" style="display: none;">
<div class="modal-card">
<div class="modal-header" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h3 class="modal-title" x-text="endpointForm.id ? (lang === 'ar' ? 'تعديل ربط تطبيق نبيه بمشروعك' : 'Edit API Endpoint Integration') : (lang === 'ar' ? 'إضافة ربط تطبيق نبيه بمشروعك' : 'Add API Endpoint Integration')"></h3>
<button class="modal-close" @click="showAddEndpointModal = false">&times;</button>
</div>
<form @submit.prevent="submitAddEndpoint()" id="form-add-endpoint">
<div class="modal-body">
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'اسم الربط' : 'Integration Name'"></label>
<input type="text" class="form-input" x-model="endpointForm.name" required placeholder="e.g. Intaleq Driver Lookup" id="add-endpoint-name">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'رابط نقطة النهاية (Endpoint URL)' : 'Endpoint URL'"></label>
<input type="url" class="form-input" x-model="endpointForm.endpoint_url" required placeholder="https://yourdomain.com/api/nabeh/action" id="add-endpoint-url">
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'نوع الإجراء (Action Type)' : 'Action Type'"></label>
<input type="text" class="form-input" x-model="endpointForm.action_type" required placeholder="e.g. verify_payment" id="add-endpoint-action-type">
<!-- Shortcuts/Hint Tags -->
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem; flex-wrap: wrap;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<button type="button" class="btn btn-secondary" style="width: auto; padding: 0.25rem 0.5rem; font-size: 0.75rem; border-radius: 4px;" @click="endpointForm.action_type = 'verify_payment'">
verify_payment (تحليل وتأكيد الدفع)
</button>
<button type="button" class="btn btn-secondary" style="width: auto; padding: 0.25rem 0.5rem; font-size: 0.75rem; border-radius: 4px;" @click="endpointForm.action_type = 'fetch_user_info'">
fetch_user_info (جلب معلومات العميل)
</button>
</div>
<span style="font-size: 0.75rem; color: var(--text-secondary); display: block; margin-top: 0.25rem;" x-text="lang === 'ar' ? 'يمكنك كتابة نوع إجراء مخصص هنا لتسهيل ربط النظام أو الضغط على الاختصارات بالأعلى.' : 'You can type a custom action type here or click the shortcuts above.'"></span>
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'الوصف' : 'Description'"></label>
<textarea class="form-input" x-model="endpointForm.description" placeholder="A brief explanation of what this endpoint does..." id="add-endpoint-description"></textarea>
</div>
<div class="form-group">
<label class="form-label" x-text="lang === 'ar' ? 'مفتاح واجهة برمجة التطبيقات (API Key)' : 'API Key'"></label>
<input type="password" class="form-input" x-model="endpointForm.api_key" placeholder="e.g. key_xxxxxxxxxxxxxxxxx" id="add-endpoint-api-key">
<span style="font-size: 0.75rem; color: var(--text-secondary); display: block; margin-top: 0.25rem;" x-text="lang === 'ar' ? 'سيتم إرسال هذا المفتاح في ترويسات X-API-Key و Authorization: Bearer عند استدعاء الخادم.' : 'This key will be automatically sent in X-API-Key and Authorization: Bearer headers for outbound server-to-server calls.'"></span>
</div>
</div>
<div class="modal-footer" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<button type="button" class="btn btn-secondary" style="width: auto;" @click="showAddEndpointModal = false" x-text="lang === 'ar' ? 'إلغاء' : 'Cancel'"></button>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="actionLoading">
<span x-show="!actionLoading" x-text="endpointForm.id ? (lang === 'ar' ? 'حفظ التعديلات' : 'Save Changes') : (lang === 'ar' ? 'إنشاء الربط' : 'Create Integration')"></span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</div>
</form>
</div>
</div>
<!-- Modal: New Template -->
<div class="modal-overlay" x-show="showNewTemplateModal" id="modal-new-template" style="display: none;">
<div class="modal-card">
<div class="modal-header">
<h3 class="modal-title">Create Message Template</h3>
<button class="modal-close" @click="showNewTemplateModal = false">&times;</button>
</div>
<form @submit.prevent="submitNewTemplate()" id="form-new-template">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Template Name</label>
<input type="text" class="form-input" x-model="templateName" required placeholder="welcome_message" id="new-template-name">
</div>
<div class="form-group">
<label class="form-label">Message Body</label>
<textarea class="form-input" x-model="templateBody" rows="4" required placeholder="Hello {{name}}, welcome to Nabeh!" id="new-template-body"></textarea>
<span style="font-size: 0.75rem; color: var(--text-secondary); display: block; margin-top: 0.25rem;">
Use {{name}} to personalize with the contact's name.
</span>
</div>
<div class="form-group">
<label class="form-label">Template Type</label>
<select class="form-input" x-model="templateType" id="new-template-type">
<option value="text">Text Only</option>
<option value="image">Image Attachment</option>
<option value="video">Video Attachment</option>
<option value="document">Document Attachment</option>
</select>
</div>
<div class="form-group" x-show="templateType !== 'text'" id="new-template-media-group">
<label class="form-label">Media URL</label>
<input type="url" class="form-input" x-model="templateMediaUrl" placeholder="https://example.com/image.jpg" id="new-template-media-url">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" style="width: auto;" @click="showNewTemplateModal = false">Cancel</button>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="actionLoading">
<span x-show="!actionLoading">Create Template</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</div>
</form>
</div>
</div>
<!-- Modal: Launch Campaign -->
<div class="modal-overlay" x-show="showLaunchCampaignModal" id="modal-launch-campaign" style="display: none;">
<div class="modal-card">
<div class="modal-header">
<h3 class="modal-title">Launch Marketing Campaign</h3>
<button class="modal-close" @click="showLaunchCampaignModal = false">&times;</button>
</div>
<form @submit.prevent="submitLaunchCampaign()" id="form-launch-campaign">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Campaign Name</label>
<input type="text" class="form-input" x-model="campaignName" required placeholder="Ramadan Campaign 2026" id="launch-campaign-name">
</div>
<div class="form-group">
<label class="form-label">Target Group</label>
<select class="form-input" x-model="campaignGroupId" required id="launch-campaign-group">
<option value="">-- Select Contact Group --</option>
<template x-for="grp in groups" :key="grp.id">
<option :value="grp.id" x-text="grp.name"></option>
</template>
</select>
</div>
<div class="form-group">
<label class="form-label">WhatsApp Session Sender</label>
<select class="form-input" x-model="campaignSessionId" required id="launch-campaign-session">
<option value="">-- Select Sender Number --</option>
<template x-if="whatsappSession">
<option :value="whatsappSession.id" x-text="whatsappSession.name + ' (' + (whatsappSession.phone || 'Connected') + ')'"></option>
</template>
</select>
</div>
<div class="form-group">
<label class="form-label">Message Template</label>
<select class="form-input" x-model="campaignTemplateId" required id="launch-campaign-template">
<option value="">-- Select Template --</option>
<template x-for="tpl in templates" :key="tpl.id">
<option :value="tpl.id" x-text="tpl.name"></option>
</template>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" style="width: auto;" @click="showLaunchCampaignModal = false">Cancel</button>
<button type="submit" class="btn btn-primary" style="width: auto;" :disabled="actionLoading">
<span x-show="!actionLoading">Launch Broadcast</span>
<span x-show="actionLoading" class="spinner"></span>
</button>
</div>
</form>
</div>
</div>
</div>
</template>
</div>
<script>
function app() {
return {
// Multi-Language State
lang: localStorage.getItem('nabeh_lang') || 'ar',
// Auth States
isLoggedIn: false,
authTab: 'login',
loginEmail: '',
loginPassword: '',
regCompanyName: '',
regUserName: '',
regEmail: '',
regPassword: '',
authError: '',
authSuccess: '',
token: null,
user: null,
// 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: 'image',
otpSessionId: '',
otpStatusMsg: '',
otpErrorCode: '',
contacts: [],
selectedContactIds: [],
bulkGroupId: '',
bulkNewGroupName: '',
templates: [],
campaigns: [],
pollingIntervalId: null,
actionLoading: false,
// Modals
showAddContactModal: false,
showNewTemplateModal: false,
showLaunchCampaignModal: false,
showAddEndpointModal: false,
// Endpoint Form
endpointForm: {
id: null,
name: '',
endpoint_url: '',
action_type: 'verify_payment',
description: '',
api_key: ''
},
endpoints: [],
// Salla Integration States
sallaStatus: null,
sallaLoading: false,
dashboardSuccess: '',
dashboardError: '',
// Forms
contactName: '',
contactPhone: '',
contactEmail: '',
contactNotes: '',
templateName: '',
templateBody: '',
templateType: 'text',
templateMediaUrl: '',
campaignName: '',
campaignGroupId: '',
campaignSessionId: '',
campaignTemplateId: '',
groups: [],
// Voice Recording States
isRecording: false,
recordingTime: 0,
recordingIntervalId: null,
mediaRecorder: null,
audioChunks: [],
generatingFromVoice: false,
chatbotSettings: {
is_active: '0',
trigger_type: 'keyword',
keyword: '',
ai_prompt: '',
gemini_api_key: ''
},
// Methods
toggleLang() {
this.lang = this.lang === 'ar' ? 'en' : 'ar';
localStorage.setItem('nabeh_lang', this.lang);
},
checkAuth() {
this.token = localStorage.getItem('nabeh_token');
const storedUser = localStorage.getItem('nabeh_user');
if (this.token && storedUser) {
this.user = JSON.parse(storedUser);
this.isLoggedIn = true;
this.initializeDashboard();
}
},
async login() {
this.actionLoading = true;
this.authError = '';
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: this.loginEmail, password: this.loginPassword })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('nabeh_token', data.token);
localStorage.setItem('nabeh_user', JSON.stringify(data.user));
this.token = data.token;
this.user = data.user;
this.isLoggedIn = true;
this.initializeDashboard();
} else {
this.authError = data.error || 'Authentication failed. Please verify credentials.';
}
} catch (err) {
this.authError = 'Connection failed. Please check server availability.';
} finally {
this.actionLoading = false;
}
},
async register() {
this.actionLoading = true;
this.authError = '';
this.authSuccess = '';
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
company_name: this.regCompanyName,
user_name: this.regUserName,
email: this.regEmail,
password: this.regPassword
})
});
const data = await response.json();
if (response.ok) {
this.authSuccess = 'Registration successful! Please log in.';
this.authTab = 'login';
this.loginEmail = this.regEmail;
// Clear form values
this.regCompanyName = '';
this.regUserName = '';
this.regEmail = '';
this.regPassword = '';
} else {
if (data.errors) {
// Extract first validation error
const firstKey = Object.keys(data.errors)[0];
this.authError = data.errors[firstKey][0];
} else {
this.authError = data.error || 'Registration failed.';
}
}
} catch (err) {
this.authError = 'Connection failed. Please try again.';
} finally {
this.actionLoading = false;
}
},
logout() {
this.stopPolling();
localStorage.removeItem('nabeh_token');
localStorage.removeItem('nabeh_user');
this.token = null;
this.user = null;
this.isLoggedIn = false;
this.whatsappSession = null;
this.whatsappSessions = [];
this.staff = [];
this.woocommerceStatus = null;
this.contacts = [];
this.templates = [];
this.campaigns = [];
this.endpoints = [];
this.sallaStatus = null;
this.dashboardSuccess = '';
this.dashboardError = '';
},
initializeDashboard() {
this.fetchWhatsappSessions();
this.fetchWhatsappStatus();
this.fetchSallaStatus();
this.fetchWooCommerceStatus();
// Set up persistent background status check
this.startPolling();
// Detect Salla success connect
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('salla_connect') === 'success') {
this.dashboardSuccess = this.lang === 'ar'
? 'تم ربط متجر سلة بنجاح!'
: 'Salla store connected successfully!';
window.history.replaceState({}, document.title, window.location.pathname);
} else if (urlParams.get('salla_connect') === 'error') {
const reason = urlParams.get('reason') || '';
this.dashboardError = this.lang === 'ar'
? 'فشل ربط متجر سلة: ' + reason
: 'Salla connection failed: ' + reason;
window.history.replaceState({}, document.title, window.location.pathname);
}
},
async fetchWhatsappStatus() {
if (!this.token) return;
const queryParam = this.whatsappSession ? `?session_id=${this.whatsappSession.id}` : '';
try {
const response = await fetch(`/api/whatsapp/status${queryParam}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.whatsappSession = data.data;
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 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', {
method: 'POST',
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');
}
} catch (err) {
alert('Error communicating with backend Gateway API.');
} finally {
this.actionLoading = false;
}
},
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', {
method: 'POST',
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') {
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;
}
},
renderQr() {
const canvasDiv = document.getElementById('qrcode-canvas');
console.log('renderQr() invoked. canvasDiv:', canvasDiv, 'Session data:', this.whatsappSession);
if (!canvasDiv || !this.whatsappSession || !this.whatsappSession.qr_code) {
return;
}
try {
if (typeof window.QRCode === 'undefined') {
throw new Error('QRCode class is not defined. Script resource failed to load.');
}
// Clear previous QR instance
canvasDiv.innerHTML = '';
new QRCode(canvasDiv, {
text: this.whatsappSession.qr_code,
width: 200,
height: 200,
colorDark: "#0b0d19",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
console.log('QR Code generated successfully.');
} catch (e) {
console.error('Error generating QR code:', e);
}
},
startPolling() {
this.stopPolling();
// Poll session state every 4 seconds
this.pollingIntervalId = setInterval(() => {
this.fetchWhatsappStatus();
}, 4000);
},
stopPolling() {
if (this.pollingIntervalId) {
clearInterval(this.pollingIntervalId);
this.pollingIntervalId = null;
}
},
// Custom API Integrations CRUD
async fetchEndpoints() {
try {
const response = await fetch('/api/endpoints', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok) {
this.endpoints = data.data || [];
}
} catch (err) {
console.error('Error fetching endpoints:', err);
}
},
async submitAddEndpoint() {
this.actionLoading = true;
try {
const response = await fetch('/api/endpoints', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify(this.endpointForm)
});
const data = await response.json();
if (response.ok) {
this.showAddEndpointModal = false;
this.endpointForm = { id: null, name: '', endpoint_url: '', action_type: 'verify_payment', description: '', api_key: '' };
await this.fetchEndpoints();
} else {
alert(data.message || 'Failed to save integration endpoint.');
}
} catch (err) {
console.error('Error saving endpoint:', err);
} finally {
this.actionLoading = false;
}
},
editEndpoint(endpoint) {
this.endpointForm = {
id: endpoint.id,
name: endpoint.name,
endpoint_url: endpoint.endpoint_url,
action_type: endpoint.action_type,
description: endpoint.description || '',
api_key: endpoint.api_key || ''
};
this.showAddEndpointModal = true;
},
async deleteEndpoint(id) {
const confirmMsg = this.lang === 'ar'
? 'هل أنت متأكد من رغبتك في حذف ربط نقطة النهاية هذا؟'
: 'Are you sure you want to delete this integration endpoint?';
if (!confirm(confirmMsg)) return;
this.actionLoading = true;
try {
const response = await fetch('/api/endpoints', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({ id: id })
});
const data = await response.json();
if (response.ok) {
await this.fetchEndpoints();
} else {
alert(data.message || 'Failed to delete endpoint.');
}
} catch (err) {
console.error('Error deleting endpoint:', err);
} finally {
this.actionLoading = false;
}
},
// Salla Integration Methods
async fetchSallaStatus() {
if (!this.token) return;
this.sallaLoading = true;
try {
const response = await fetch('/api/integrations/salla/status', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.sallaStatus = data;
} else {
this.sallaStatus = { connected: false };
}
} catch (err) {
console.error('Error fetching Salla status:', err);
this.sallaStatus = { connected: false };
} finally {
this.sallaLoading = false;
}
},
connectSalla() {
if (!this.token) return;
window.location.href = `/api/integrations/salla/auth?token=${encodeURIComponent(this.token)}`;
},
async disconnectSalla() {
const confirmMsg = this.lang === 'ar'
? 'هل أنت متأكد من رغبتك في إلغاء ربط متجر سلة؟'
: 'Are you sure you want to disconnect your Salla store?';
if (!confirm(confirmMsg)) return;
this.sallaLoading = true;
try {
const response = await fetch('/api/integrations/salla/disconnect', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.dashboardSuccess = this.lang === 'ar'
? 'تم إلغاء ربط متجر سلة بنجاح.'
: 'Salla store disconnected successfully.';
await this.fetchSallaStatus();
} else {
this.dashboardError = data.message || 'Failed to disconnect Salla integration.';
}
} catch (err) {
console.error('Error disconnecting Salla:', err);
this.dashboardError = 'Failed to disconnect Salla integration.';
} finally {
this.sallaLoading = false;
}
},
// CRM List Fetchers
async fetchContacts() {
this.selectedContactIds = [];
this.bulkGroupId = '';
this.bulkNewGroupName = '';
try {
const response = await fetch('/api/contacts', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok) {
this.contacts = data.contacts || data.data || [];
}
} catch (err) {
console.error('Error fetching contacts:', err);
}
},
async applyBulkGroup() {
if (this.selectedContactIds.length === 0) {
alert('Please select at least one contact first.');
return;
}
if (!this.bulkGroupId && !this.bulkNewGroupName.trim()) {
alert('Please select an existing group or type a new group name.');
return;
}
this.actionLoading = true;
try {
let groupId = this.bulkGroupId;
// If a new group name is typed, create the group first
if (this.bulkNewGroupName.trim()) {
const newGroupRes = await fetch('/api/groups', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({ name: this.bulkNewGroupName.trim() })
});
const newGroupData = await newGroupRes.json();
if (!newGroupRes.ok) {
throw new Error(newGroupData.message || newGroupData.error || 'Failed to create group');
}
groupId = newGroupData.id;
}
// Attach selected contacts in bulk
const bulkRes = await fetch('/api/groups/bulk-add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({
group_id: groupId,
contact_ids: this.selectedContactIds
})
});
const bulkData = await bulkRes.json();
if (bulkRes.ok) {
alert('Successfully added selected contacts to the group!');
this.selectedContactIds = [];
this.bulkGroupId = '';
this.bulkNewGroupName = '';
await this.fetchGroups();
} else {
alert(bulkData.error || bulkData.message || 'Failed to apply grouping.');
}
} catch (err) {
console.error('Error applying bulk grouping:', err);
alert(err.message || 'An error occurred during bulk grouping.');
} finally {
this.actionLoading = false;
}
},
async fetchTemplates() {
try {
const response = await fetch('/api/templates', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok) {
this.templates = data.templates || data.data || [];
}
} catch (err) {
console.error('Error fetching templates:', err);
}
},
async fetchCampaigns() {
try {
const response = await fetch('/api/campaigns', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok) {
this.campaigns = data.campaigns || data.data || [];
}
} catch (err) {
console.error('Error fetching campaigns:', err);
}
},
async fetchGroups() {
try {
const response = await fetch('/api/groups', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok) {
this.groups = data.groups || data.data || [];
}
} catch (err) {
console.error('Error fetching groups:', err);
}
},
async fetchChatbotSettings() {
try {
const response = await fetch('/api/chatbot/rules', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok && data.data && data.data.length > 0) {
const rule = data.data[0];
this.chatbotSettings.is_active = rule.is_active.toString();
this.chatbotSettings.trigger_type = rule.trigger_type;
this.chatbotSettings.keyword = rule.keyword || '';
this.chatbotSettings.ai_prompt = rule.ai_prompt || '';
this.chatbotSettings.gemini_api_key = rule.gemini_api_key ? '••••••••' : '';
} else {
this.chatbotSettings = {
is_active: '0',
trigger_type: 'keyword',
keyword: '',
ai_prompt: '',
gemini_api_key: ''
};
}
} catch (err) {
console.error('Error fetching chatbot settings:', err);
}
},
loadPromptTemplate(type) {
if (type === 'intaleq') {
this.chatbotSettings.ai_prompt = `أنت روبوت خدمة العملاء الخاص بشركة "انطلق" (Intaleq). مهمتك هي مساعدة المستخدمين والإجابة على استفساراتهم حول خدماتنا بلهجة سورية ودودة ومهنية للغاية.
التزم بالقواعد والتفاصيل التالية بدقة عند الرد:
1. اسم الشركة: انطلق.
2. ساعات العمل: يومياً من الساعة 11 صباحاً حتى 5 مساءً.
3. طرق الدفع المتاحة: شام كاش، سيريتل، إم تي إن، وبطاقات البنك.
4. طريقة التسجيل: يمكن للمستخدمين تحميل التطبيق من متجر جوجل بلاي، آبل ستور، أو عبر رابط مباشر. عملية التسجيل سهلة وسريعة.
5. مميزات التطبيق:
- عمولة التطبيق هي 10% فقط (وهي الأقل في السوق).
- يوجد ذكاء اصطناعي متطور لتحليل المشاكل وحلها.
- ضمان حقوق السائق والركاب بشكل كامل.
- خدمة الطلب من أي مكان وبكل سهولة.
6. تحدث دائماً باللهجة السورية اللطيفة والترحيبية والودية عند التحدث باللغة العربية (مثال: "ياهلا بك"، "كيف بقدر أساعدك اليوم؟").
7. كوني مختصرة ومباشرة ومفيدة في إجاباتكِ وتجنبي الإطالة غير الضرورية.`;
} else if (type === 'nabeh') {
this.chatbotSettings.ai_prompt = `أنتِ "سارة"، موظفة خدمة العملاء الافتراضية الذكية والودودة لتطبيق "نبيه" (Nabeh).
مهمتكِ هي مساعدة المستخدمين والإجابة على استفساراتهم بلطف وأدب بالمسائل التقنية والتجارية المتعلقة بالتطبيق.
اتبعي القواعد التالية بدقة عند الرد:
1. في أول رسالة تواصل مع العميل، عرّفي عن نفسكِ دائماً بالقول: "معك سارة من فريق تطبيق نبيه، كيف بقدر أساعدك اليوم؟" (باللهجة السورية الودية).
2. تحدثي دائماً باللهجة السورية اللطيفة والترحيبية والودية عند التحدث باللغة العربية.
3. إذا سأل العميل أو تحدث باللغة الإنجليزية، فتحدثي معه باللغة الإنجليزية بشكل احترافي وودي وحافظي على نفس الهوية والمساعدة.
4. كوني مختصرة ومباشرة في إجاباتكِ وتجنبي الإطالة غير الضرورية.
5. ساعدي المستخدمين في فهم ميزات تطبيق "نبيه" لإدارة وتسهيل حملات واتساب والردود الذكية.`;
} else if (type === 'store') {
this.chatbotSettings.ai_prompt = `أنت موظف خدمة عملاء ذكي ومرحب لمتجرنا الإلكتروني.
مهمتك هي مساعدة العملاء والإجابة على استفساراتهم حول المنتجات، الشحن، والطلبات بلطف وأدب.
اتبع القواعد التالية عند الرد:
1. رحب بالعميل دائماً بشكل ودّي وسريع في بداية المحادثة.
2. أجب عن الأسئلة بدقة واختصار.
3. إذا سأل العميل عن حالة طلب، اطلب منه تزويدك برقم الطلب للتحقق منه.
4. حافظ على نبرة إيجابية ومحترفة.`;
} else if (type === 'general') {
this.chatbotSettings.ai_prompt = `You are a professional and helpful customer support assistant.
Your goal is to assist users with their general inquiries, troubleshoot issues, and provide helpful guidance.
Guidelines:
1. Be polite, clear, and professional.
2. Provide concise and accurate information.
3. If you do not know the answer, politely ask the user to wait while you check with the team.`;
}
},
async saveChatbotSettings() {
this.actionLoading = true;
try {
const payload = {
is_active: this.chatbotSettings.is_active === '1',
trigger_type: this.chatbotSettings.trigger_type,
keyword: this.chatbotSettings.keyword,
ai_prompt: this.chatbotSettings.ai_prompt,
gemini_api_key: this.chatbotSettings.gemini_api_key,
session_id: this.whatsappSession ? this.whatsappSession.id : null
};
const response = await fetch('/api/chatbot/rules', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const data = await response.json();
if (response.ok) {
alert('Chatbot settings saved successfully.');
await this.fetchChatbotSettings();
} else {
alert(data.message || 'Failed to save chatbot settings.');
}
} catch (err) {
alert('Error communicating with backend API.');
} finally {
this.actionLoading = false;
}
},
formatRecordTime(seconds) {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}:${s < 10 ? '0' : ''}${s}`;
},
async toggleVoiceRecording() {
if (this.isRecording) {
await this.stopVoiceRecording();
} else {
await this.startVoiceRecording();
}
},
async startVoiceRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioChunks = [];
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
this.audioChunks.push(event.data);
}
};
this.mediaRecorder.onstop = async () => {
const mimeType = this.mediaRecorder.mimeType || 'audio/webm';
const audioBlob = new Blob(this.audioChunks, { type: mimeType });
// Stop all audio tracks to release microphone
stream.getTracks().forEach(track => track.stop());
await this.sendAudioToBackend(audioBlob);
};
this.mediaRecorder.start();
this.isRecording = true;
this.recordingTime = 0;
this.recordingIntervalId = setInterval(() => {
this.recordingTime++;
if (this.recordingTime >= 180) { // 3 minutes max limit
this.stopVoiceRecording();
}
}, 1000);
} catch (err) {
console.error('Error starting audio recording:', err);
alert('تعذر الوصول إلى الميكروفون. يرجى التحقق من صلاحيات المتصفح.');
}
},
async stopVoiceRecording() {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
}
if (this.recordingIntervalId) {
clearInterval(this.recordingIntervalId);
this.recordingIntervalId = null;
}
this.isRecording = false;
},
async sendAudioToBackend(audioBlob) {
this.generatingFromVoice = true;
try {
const formData = new FormData();
formData.append('audio', audioBlob, 'voice_instruction.webm');
const response = await fetch('/api/chatbot/generate-prompt-from-audio', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`
},
body: formData
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.chatbotSettings.ai_prompt = data.prompt;
// Automatically save the generated prompt in database
await this.saveChatbotSettings();
alert('تم توليد التوجيهات وحفظها بنجاح!');
} else {
alert(data.message || 'فشلت عملية صياغة التوجيهات من الصوت.');
}
} catch (err) {
console.error('Error sending audio to backend:', err);
alert('حدث خطأ أثناء التواصل مع السيرفر لصياغة التوجيهات.');
} finally {
this.generatingFromVoice = false;
}
},
openAddContactModal() {
this.contactName = '';
this.contactPhone = '';
this.contactEmail = '';
this.contactNotes = '';
this.showAddContactModal = true;
},
async submitAddContact() {
this.actionLoading = true;
try {
const response = await fetch('/api/contacts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: this.contactName,
phone: this.contactPhone,
email: this.contactEmail,
notes: this.contactNotes
})
});
const data = await response.json();
if (response.ok) {
this.showAddContactModal = false;
await this.fetchContacts();
} else {
alert(data.error || 'Failed to create contact.');
}
} catch (err) {
alert('Connection error.');
} finally {
this.actionLoading = false;
}
},
openNewTemplateModal() {
this.templateName = '';
this.templateBody = '';
this.templateType = 'text';
this.templateMediaUrl = '';
this.showNewTemplateModal = true;
},
async submitNewTemplate() {
this.actionLoading = true;
try {
const response = await fetch('/api/templates', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: this.templateName,
body: this.templateBody,
type: this.templateType,
media_url: this.templateMediaUrl
})
});
const data = await response.json();
if (response.ok) {
this.showNewTemplateModal = false;
await this.fetchTemplates();
} else {
alert(data.error || 'Failed to create template.');
}
} catch (err) {
alert('Connection error.');
} finally {
this.actionLoading = false;
}
},
async openLaunchCampaignModal() {
this.campaignName = '';
this.campaignGroupId = '';
this.campaignSessionId = this.whatsappSession ? this.whatsappSession.id : '';
this.campaignTemplateId = '';
await this.fetchGroups();
await this.fetchTemplates();
this.showLaunchCampaignModal = true;
},
async submitLaunchCampaign() {
this.actionLoading = true;
try {
const response = await fetch('/api/campaigns', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: this.campaignName,
group_id: this.campaignGroupId,
session_id: this.campaignSessionId,
template_id: this.campaignTemplateId
})
});
const data = await response.json();
if (response.ok) {
this.showLaunchCampaignModal = false;
await this.fetchCampaigns();
} else {
alert(data.error || 'Failed to launch campaign.');
}
} catch (err) {
alert('Connection error.');
} finally {
this.actionLoading = false;
}
}
}
}
</script>
</body>
</html>