import { useState, useCallback, useEffect } from 'react'; import type { DashboardData } from './types'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './components/ui/table'; import { Badge } from './components/ui/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from './components/ui/dropdown-menu'; import { Plus, RefreshCw, Trash2, Edit, Send, MoreHorizontal, User, Smartphone, PenBox, Type } from 'lucide-react'; import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from './components/ui/dialog'; import { Sheet, SheetContent } from './components/ui/sheet'; import { Label } from './components/ui/label'; import { MultiSelectTagify } from './components/ui/multi-select-tagify'; import { toast } from 'sonner'; import { CheckCircle2, Key, Hash, BookOpen } from 'lucide-react'; import { SettingsLayout, SettingsHeader, AdminButton, SecondaryButton, GhostButton, ModernCardHeader, SettingCard, SettingInput, StatusMessage, SaveWithStatus, SaveFooter } from './components/ui/settings-ui'; import { useSettingsSave } from './hooks/useSettingsSave'; interface InstanceData { id: number; name: string; instance_id: string; access_token: string; status: string; message: string; } export default function WhatsAppWebSender({ data: dashboardData }: { data: DashboardData }) { const t = useCallback((key: string, fallback: string): string => { const val = (dashboardData?.i18n as Record)?.[key]; return typeof val === 'string' ? val : fallback; }, [dashboardData.i18n]); const [instances, setInstances] = useState(dashboardData?.instancesData?.instances || []); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isTestSuccessModalOpen, setIsTestSuccessModalOpen] = useState(false); const [isMeModalOpen, setIsMeModalOpen] = useState(false); const [meData, setMeData] = useState(null); const [currentInstance, setCurrentInstance] = useState(null); const [isAdding, setIsAdding] = useState(false); const [isUpdating, setIsUpdating] = useState(false); // Form states const [formData, setFormData] = useState({ name: '', instance_id: '', access_token: '', phone_number: '', message: '' }); const [mappings, setMappings] = useState({ login: [] as string[], signup: [] as string[], general: [] as string[], notif_user: [] as string[], notif_admin: [] as string[], }); const [features, setFeatures] = useState({ otp_login: true, otp_signup: true, notifications: true, }); const fetchInstances = useCallback(async () => { try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web?_=${Date.now()}`, { headers: { 'X-WP-Nonce': dashboardData.global.wpRestNonce } }); const result = await response.json(); if (result.success) { setInstances(result.data || []); } } catch (error) { console.error('Failed to load instances', error); } }, [dashboardData.global.apiRestUrl, dashboardData.global.wpRestNonce]); useEffect(() => { const timer = setTimeout(() => { fetchInstances(); }, 0); return () => clearTimeout(timer); }, [fetchInstances]); const syncData = useCallback(() => { if (!dashboardData) return; const currentInstances = dashboardData.instancesData?.instances || instances; const onlineInst = currentInstances.find((i: InstanceData) => i.status?.toLowerCase() === 'online') || currentInstances[0]; const onlineId = onlineInst?.id || ''; const resolveToInternalId = (val: unknown): string[] => { let rawList: string[] = []; if (Array.isArray(val)) rawList = val.map(String).filter(Boolean); else if (typeof val === 'string' && val.trim() !== '') rawList = val.split(',').filter(Boolean); else if (val !== undefined && val !== null && val !== '' && val !== 0) rawList = [String(val)]; const resolved = rawList.map(item => { const match = currentInstances.find((inst: InstanceData) => String(inst.id) === item || inst.instance_id === item); return match ? String(match.id) : item; }); // Remove duplicates return Array.from(new Set(resolved)); }; const savedLogin = resolveToInternalId(dashboardData.otp?.instance); const savedSignup = resolveToInternalId(dashboardData.signup?.selected_instance); const savedNotifUser = resolveToInternalId(dashboardData.notif?.selected_instance_ids); const savedNotifAdmin = resolveToInternalId(dashboardData.notif?.admin_selected_instance_ids); const savedGeneral = resolveToInternalId(dashboardData.general_instance); setMappings({ login: savedLogin.length > 0 ? savedLogin : (onlineId ? [String(onlineId)] : []), signup: savedSignup.length > 0 ? savedSignup : (onlineId ? [String(onlineId)] : []), notif_user: savedNotifUser.length > 0 ? savedNotifUser : (onlineId ? [String(onlineId)] : []), notif_admin: savedNotifAdmin.length > 0 ? savedNotifAdmin : (onlineId ? [String(onlineId)] : []), general: savedGeneral.length > 0 ? savedGeneral : (onlineId ? [String(onlineId)] : []) }); setFeatures({ notifications: !!dashboardData.notif?.enabled, otp_login: !!(dashboardData.otp?.enabled && dashboardData.otp?.login_enabled), otp_signup: !!(dashboardData.otp?.enabled && dashboardData.otp?.signup_enabled), }); }, [dashboardData, instances]); useEffect(() => { const timer = setTimeout(() => { syncData(); }, 0); return () => clearTimeout(timer); }, [syncData]); const { saveStatus, hasUnsavedChanges, isSaving: isSavingMappings, handleSave, markDirty } = useSettingsSave({ onSave: async () => { try { const response = await fetch(`${dashboardData.global.settingsRestUrl}?_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ settings: { wawp_otp_settings: { ...dashboardData.otp, instance: mappings.login.join(',') }, wawp_signup_settings: { ...dashboardData.signup, selected_instance: mappings.signup.join(',') }, wawp_general_instance: mappings.general.join(','), wawp_notif_selected_instance_ids: mappings.notif_user.join(','), wawp_notif_admin_selected_instance_ids: mappings.notif_admin.join(',') } }) }); const result = await response.json(); if (result.success) { window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'whatsapp-web-sender' } })); return true; } return false; } catch (error) { console.error('WhatsApp mappings save error:', error); return false; } } }); const updateMappings = (key: keyof typeof mappings, value: string[]) => { setMappings(prev => ({ ...prev, [key]: value })); markDirty(); }; const handleAddInstance = async () => { if (isAdding) return; setIsAdding(true); const loadingToast = toast.loading(t('addingInstanceToast', 'Adding instance...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web?_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ account_name: formData.name, config: { instance_id: formData.instance_id, access_token: formData.access_token } }) }); const result = await response.json(); if (result.success) { toast.success(t('instanceAddedToast', 'Instance added successfully'), { id: loadingToast }); setIsAddModalOpen(false); setFormData({ ...formData, name: '', instance_id: '', access_token: '' }); fetchInstances(); window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'whatsapp-web-sender' } })); } else { toast.error(result.message || t('failedAddInstanceToast', 'Failed to add instance'), { id: loadingToast }); } } catch { toast.error(t('errorOccurredToast', 'An error occurred'), { id: loadingToast }); } finally { setIsAdding(false); } }; const handleEditInstance = async () => { if (!currentInstance || isUpdating) return; setIsUpdating(true); const loadingToast = toast.loading(t('updatingInstanceToast', 'Updating instance...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web?_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ id: currentInstance.id, account_name: formData.name, config: { instance_id: formData.instance_id, access_token: formData.access_token } }) }); const result = await response.json(); if (result.success) { toast.success(t('instanceUpdatedToast', 'Instance updated successfully'), { id: loadingToast }); setIsEditModalOpen(false); fetchInstances(); window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'whatsapp-web-sender' } })); } else { toast.error(result.message || t('failedUpdateInstanceToast', 'Failed to update instance'), { id: loadingToast }); } } catch { toast.error(t('errorOccurredToast', 'An error occurred'), { id: loadingToast }); } finally { setIsUpdating(false); } }; const handleDeleteInstance = useCallback(async (id: number) => { if (!confirm(t('confirmDeleteInstance', 'Are you sure you want to delete this instance?'))) return; const loadingToast = toast.loading(t('deletingInstanceToast', 'Deleting instance...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web/${id}?_=${Date.now()}`, { method: 'DELETE', headers: { 'X-WP-Nonce': dashboardData.global.wpRestNonce } }); const result = await response.json(); if (result.success) { toast.success(t('instanceDeletedToast', 'Instance deleted'), { id: loadingToast }); fetchInstances(); window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'whatsapp-web-sender' } })); } else { toast.error(result.message || t('failedDeleteInstanceToast', 'Failed to delete instance'), { id: loadingToast }); } } catch { toast.error(t('errorOccurredToast', 'An error occurred'), { id: loadingToast }); } }, [dashboardData, fetchInstances, t]); const handleUpdateStatus = useCallback(async (inst: InstanceData) => { const loadingToast = toast.loading(t('refreshingInstanceStatusToast', 'Refreshing instance status...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web/ops?_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ op: 'status', instance_id: inst.instance_id, access_token: inst.access_token }) }); const result = await response.json(); if (result.success) { toast.success(t('statusUpdatedToast', 'Status updated'), { id: loadingToast }); fetchInstances(); } else { toast.error(result.message || t('failedCheckStatusToast', 'Failed to check status'), { id: loadingToast }); } } catch { toast.error(t('errorOccurredToast', 'An error occurred'), { id: loadingToast }); } }, [dashboardData, fetchInstances, t]); const handleSendTestDelivery = useCallback(async (inst: InstanceData) => { const targetNumber = '447441429009'; const greetings = ['Hi', 'Hello', 'Hey', 'Greetings']; const greeting = greetings[Math.floor(Math.random() * greetings.length)]; const randomRef = Math.random().toString(36).substring(7).toUpperCase(); const randomCode = Math.floor(1000 + Math.random() * 9000); const message = `${greeting} From Wawp Plugin.\nThis test message and my site can work now.\n\n[Ref: ${randomRef}, Code: ${randomCode}]`; const loadingToast = toast.loading(t('sendingTestDeliveryToast', 'Sending secure test delivery...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web/ops?_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ op: 'test', instance_id: inst.instance_id, access_token: inst.access_token, phone_number: targetNumber, message: message }) }); const result = await response.json(); if (result.success) { toast.success(t('testMessageSentToast', 'Test message sent'), { id: loadingToast }); setIsTestSuccessModalOpen(true); } else { toast.error(result.message || t('failedSendTestToast', 'Failed to send test message'), { id: loadingToast }); } } catch { toast.error(t('errorOccurredToast', 'An error occurred'), { id: loadingToast }); } }, [dashboardData, t]); const handleMe = useCallback(async (inst: InstanceData) => { const loadingToast = toast.loading(t('fetchingAccountDataToast', 'Fetching account data...')); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/whatsapp_web/ops`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ op: 'me', instance_id: inst.instance_id, access_token: inst.access_token }) }); const result = await response.json(); if (result.success) { setMeData(result.data); setIsMeModalOpen(true); toast.success(t('dataFetchedToast', 'Data fetched successfully'), { id: loadingToast }); } else { toast.error(result.message || t('failedFetchDataToast', 'Failed to fetch data.'), { id: loadingToast }); } } catch { toast.error(t('errorFetchDataToast', 'An error occurred while fetching data.'), { id: loadingToast }); } }, [dashboardData, t]); const getStatusBadge = (status: string, message: string) => { let st = status?.toLowerCase() || 'unknown'; let bg = 'bg-slate-100'; let text = 'text-slate-600'; let dot = 'bg-slate-400'; // Per user request: If WORKING, show as ONLINE if (st === 'working' || st === 'online' || st === 'ready') { st = 'online'; bg = 'bg-emerald-50'; text = 'text-emerald-700'; dot = 'bg-emerald-500'; } else if (st === 'offline' || st === 'failed' || st === 'stopped') { bg = 'bg-rose-50'; text = 'text-rose-700'; dot = 'bg-rose-500'; } else if (st === 'checking' || st === 'paused') { bg = 'bg-amber-50'; text = 'text-amber-700'; dot = 'bg-amber-500'; } else if (st === 'scan_qr_code' || st === 'scan qr') { bg = 'bg-sky-50'; text = 'text-sky-700'; dot = 'bg-sky-500'; } const isError = st === 'offline' || st === 'failed' || st === 'stopped'; return (
{st === 'scan qr' || st === 'scan_qr_code' ? t('scanQrCode', 'Scan QR Code') : t(st, st)}
{isError && message && {message}}
); }; return (
{/* Header */}
handleSave(false)} /> window.open(`https://help.wawp.net/${dashboardData?.rtl ? 'ar' : 'en'}/articles/how-to-connect-whatsapp-web`, '_blank')} title={dashboardData?.rtl ? 'دليل ربط واتساب ويب' : 'WhatsApp Web Guide'} >
{/* Connected Numbers Card */} {t('waWebSlotsDesc', 'Connect and manage your WhatsApp Web instances for automated notifications.')} } rightAction={
setIsAddModalOpen(true)} icon={Plus}> {t('addNewInstance', 'Add New Instance')}
setFormData({ ...formData, name: val })} placeholder="" icon={Type} /> setFormData({ ...formData, instance_id: val })} placeholder="" icon={Hash} /> setFormData({ ...formData, access_token: val })} placeholder="" icon={Key} type="password" />
setIsAddModalOpen(false)} className="flex-1"> {t('close', 'Close')} {t('save', 'Connect')}
} />
{/* Mobile View: Cards */}
{instances.length === 0 ? (

{t('noInstancesFound', 'No Instances Found')}

) : ( instances.map((inst) => (
{inst.name} {inst.instance_id}
handleSendTestDelivery(inst)}> {t('sendTestMessage', 'Send Test Delivery')} handleUpdateStatus(inst)}> {t('checkConnection', 'Force Status Link')}
{ setCurrentInstance(inst); setFormData({ ...formData, name: inst.name, instance_id: inst.instance_id, access_token: inst.access_token }); setIsEditModalOpen(true); }}> {t('edit', 'Modify Credentials')} handleMe(inst)}> {t('aboutData', 'Inspect Session Data')}
handleDeleteInstance(inst.id)}> {t('delete', 'Disconnect Instance')}
{t('securityToken', 'Security Token')} ••••••••{inst.access_token?.slice(-4)}
{getStatusBadge(inst.status, inst.message)}
)) )}
{/* Desktop View: Table */}
{t('name', 'Name')} {t('instanceId', 'Instance ID')} {t('accessToken', 'Access Token')} {t('status', 'Status')} {t('actions', 'Actions')} {instances.length === 0 ? (

{t('noInstancesFound', 'No Instances Found')}

{t('connectFirstWa', 'Connect your first WhatsApp number to begin.')}

) : ( instances.map((inst) => ( {inst.name} {inst.instance_id} ••••••••{inst.access_token?.slice(-4)}
{getStatusBadge(inst.status, inst.message)}
handleSendTestDelivery(inst)} className="hover:bg-emerald-50 hover:text-emerald-600 border border-transparent hover:border-emerald-100 rounded-[5px]" > { setCurrentInstance(inst); setFormData({ ...formData, name: inst.name, instance_id: inst.instance_id, access_token: inst.access_token }); setIsEditModalOpen(true); }} className="hover:bg-blue-50 hover:text-blue-600 border border-transparent hover:border-blue-100 rounded-[5px]" > handleSendTestDelivery(inst)}> {t('sendTestMessage', 'Send Test Delivery')} handleUpdateStatus(inst)}> {t('checkConnection', 'Force Status Link')}
{ setCurrentInstance(inst); setFormData({ ...formData, name: inst.name, instance_id: inst.instance_id, access_token: inst.access_token }); setIsEditModalOpen(true); }}> {t('edit', 'Modify Credentials')} handleMe(inst)}> {t('aboutData', 'Inspect Session Data')}
handleDeleteInstance(inst.id)}> {t('delete', 'Disconnect Instance')}
)) )}
{/* Feature Routing Card */}
{features.notifications && ( <>

{t('notifUserDesc', 'Choose numbers for sending notifications to USERS/Customers.')}

({ label: `${inst.name} (${inst.instance_id})`, value: String(inst.id) }))} selected={mappings.notif_user} onChange={(selected) => updateMappings('notif_user', selected)} placeholder={t('selectSendersPlaceholder', 'Select senders...')} className="rounded-[5px] border-slate-200 bg-white" />
0 ? 'active' : 'error'} message={mappings.notif_user.length > 0 ? t('customerNotifActiveMessage', '{count} instance(s) will handle customer notifications. Multi-sender routing ensures high delivery rates.').replace('{count}', String(mappings.notif_user.length)) : t('customerNotifInactiveMessage', 'Critical: No senders selected for customer notifications. High risk of delivery failure.')} />

{t('notifAdminDesc', 'Choose numbers for sending notification events to SITE ADMINS.')}

({ label: `${inst.name} (${inst.instance_id})`, value: String(inst.id) }))} selected={mappings.notif_admin} onChange={(selected) => updateMappings('notif_admin', selected)} placeholder={t('selectSendersPlaceholder', 'Select senders...')} className="rounded-[5px] border-slate-200 bg-white" />
0 ? 'active' : 'error'} message={mappings.notif_admin.length > 0 ? t('adminNotifActiveMessage', '{count} instance(s) will handle system alerts. Ensuring admins stay informed about site events.').replace('{count}', String(mappings.notif_admin.length)) : t('adminNotifInactiveMessage', 'Warning: No senders selected for admin notifications. You might miss important system alerts.')} />
)}

{t('primaryTransactionalFlows', 'Primary Transactional Flows')}

{features.otp_login && (

{t('verificationDeliveryDesc', 'Verification delivery for existing accounts.')}

({ label: `${inst.name} (${inst.instance_id})`, value: String(inst.id) }))} selected={mappings.login} onChange={(selected) => updateMappings('login', selected)} placeholder={t('selectSendersPlaceholder', 'Select senders...')} className="rounded-[5px] border-slate-200 bg-white" />
0 ? 'active' : 'inactive'} message={mappings.login.length > 0 ? t('loginOtpActiveMessage', 'Secure OTP delivery is active for existing user accounts.') : t('loginOtpInactiveMessage', 'Login routing is disabled. Users will fallback to default methods.')} />
)} {features.otp_signup && (

{t('newRegistrationDesc', 'Authentication for new registration attempts.')}

({ label: `${inst.name} (${inst.instance_id})`, value: String(inst.id) }))} selected={mappings.signup} onChange={(selected) => updateMappings('signup', selected)} placeholder={t('selectSendersPlaceholder', 'Select senders...')} className="rounded-[5px] border-slate-200 bg-white" />
0 ? 'active' : 'inactive'} message={mappings.signup.length > 0 ? t('signupOtpActiveMessage', 'New user verification is active. Ensuring only valid numbers can register.') : t('signupOtpInactiveMessage', 'Registration routing is disabled. OTP will not be sent to new users via WhatsApp.')} />
)}

{t('generalSystemSenderDesc', 'Used for system operations, direct messages, and general automation tasks.')}

({ label: `${inst.name} (${inst.instance_id})`, value: String(inst.id) }))} selected={mappings.general} onChange={(selected) => updateMappings('general', selected)} placeholder={t('selectSendersPlaceholder', 'Select senders...')} className="rounded-[5px] border-slate-200 bg-white" />
0 ? 'active' : 'inactive'} message={mappings.general.length > 0 ? t('generalActiveMessage', 'System sender is active for general automation and background tasks.') : t('generalInactiveMessage', 'No general sender selected. Some automated background tasks might fail.')} />
{/* Modals */}
setFormData({ ...formData, name: val })} placeholder="" icon={Type} /> setFormData({ ...formData, instance_id: val })} placeholder="" icon={Hash} /> setFormData({ ...formData, access_token: val })} placeholder="" icon={Key} type="password" />
setIsEditModalOpen(false)} disabled={isUpdating}> {t('close', 'Close')} {t('updateConfigBtn', 'Update Config')}
Test Delivery Success Your test WhatsApp message has been sent successfully.
{/* Perfect Setup Wizard Success Icon */}

{t('done', 'DONE')}

{t('checkPhoneNow', 'Check your phone now!')}

{t('testDeliverySentTo', 'Test delivery sent successfully to')}{' '} 447441429009

window.open(`https://wa.me/447441429009`, '_blank')} > {t('openWaPreview', 'Open WhatsApp to Preview')}
setIsTestSuccessModalOpen(false)}> {t('close', 'Close')}
{t('remoteAccountDetails', 'Remote Account Details')} {t('remoteAccountDetailsDesc', 'Live session and account metadata from the global API.')}
                  {JSON.stringify(meData, null, 2)}
                
setIsMeModalOpen(false)}>{t('closeMetadataInspector', 'Close Metadata Inspector')}
handleSave(false)} docsUrl={`https://help.wawp.net/${dashboardData?.rtl ? 'ar' : 'en'}/articles/how-to-connect-whatsapp-web`} docsTitle={dashboardData?.rtl ? "كيفية ربط واتساب ويب" : "How to Connect WhatsApp Web"} /> ); }