import { useState, useEffect, lazy, Suspense, useCallback, useMemo, useRef, useTransition } from 'react' import MainLayout from './MainLayout' import type { DashboardData, SidebarData, Notice } from './types' import { LoadingSkeleton } from './components/ui/LoadingSkeleton' import { SafeComponent } from './components/ui/SafeComponent' import { TooltipProvider } from "@/components/ui/tooltip" // Lazy load components for code splitting const Dashboard = lazy(() => import('./Dashboard')) const ActivityLogs = lazy(() => import('./ActivityLogs')) const SystemInfo = lazy(() => import('./SystemInfo')) const Connector = lazy(() => import('./Connector')) const NotificationsBuilder = lazy(() => import('./NotificationsBuilder')) const EmailTemplates = lazy(() => import('./EmailTemplates')) const Campaigns = lazy(() => import('./Campaigns')) const ChatWidget = lazy(() => import('./ChatWidget')) const PhoneFieldSettings = lazy(() => import('./PhoneFieldSettings')) const GoogleRecaptchaSettings = lazy(() => import('./GoogleRecaptchaSettings')) const AuthenticationPagesSettings = lazy(() => import('./AuthenticationPagesSettings')) const CheckoutVerificationSettings = lazy(() => import('./CheckoutVerificationSettings')) const RegistrationFormSettings = lazy(() => import('./RegistrationFormSettings')) const PasswordlessLoginSettings = lazy(() => import('./PasswordlessLoginSettings')) const AbandonedCartSettings = lazy(() => import('./AbandonedCartSettings')) const AbandonedCartSessions = lazy(() => import('./AbandonedCartSessions')) const Senders = lazy(() => import('./Senders')) const WhatsAppWebSender = lazy(() => import('./WhatsAppWebSender')) const MetaAPISender = lazy(() => import('./MetaAPISender')) const FirebaseSender = lazy(() => import('./FirebaseSender')) const SMTPSender = lazy(() => import('./SMTPSender')) const BlockManager = lazy(() => import('./BlockManager')) const SetupWizard = lazy(() => import('./SetupWizard')) export function App() { const [ready, setReady] = useState(false) const [currentSection, setCurrentSection] = useState('dashboard') const [fetchedData, setFetchedData] = useState>({}) const [isDataLoading, setIsDataLoading] = useState(!window.wawpDashboardData) const [isPending, startTransition] = useTransition(); const isFetchingRef = useRef>({}); const rawData = window.wawpDashboardData; const enrichedData = useMemo(() => { if (!rawData) return null; const ajaxUrl = rawData.ajaxUrl || rawData.global?.ajaxUrl || window.ajaxurl; const merged = { ...rawData, ...fetchedData, sidebarData: { ...(rawData?.sidebarData || {}), ...(fetchedData?.sidebarData || {}), enabledSections: { ...((rawData as DashboardData)?.sidebarData?.enabledSections || {}), ...((fetchedData as DashboardData)?.sidebarData?.enabledSections || {}) }, enabledSenders: { ...((rawData as DashboardData)?.sidebarData?.enabledSenders || {}), ...((fetchedData as DashboardData)?.sidebarData?.enabledSenders || {}) } }, i18n: { ...(rawData?.i18n || {}), ...(fetchedData?.i18n || {}) }, global: { ...(rawData.global || {}), ...(fetchedData.global || {}), ajaxUrl, section: currentSection } } as DashboardData; return merged; }, [rawData, currentSection, fetchedData]); // Sync window.wawpDashboardData for non-React components or hooks reading from global window useEffect(() => { if (enrichedData) { window.wawpDashboardData = enrichedData; } }, [enrichedData]); const fetchSectionData = useCallback(async (sectionWithParams: string, force = false) => { if (!sectionWithParams) return; const [section] = sectionWithParams.split('&'); if (isFetchingRef.current[sectionWithParams] && !force) return; const sectionToDataKey: Record = { 'whatsapp-web-sender': 'instancesData', 'meta-api-sender': 'metaApiSenderData', 'firebase-sender': 'firebaseSenderData', 'smtp-sender': 'smtpSenderData', 'block-manager': 'blockManagerData', 'notifications': 'notificationsData', 'chat_widget': 'chatWidgetData', 'country-code': 'phoneFieldData', 'abandoned_carts_settings': 'abandonedCartsSettings', 'google_recaptcha': 'recaptchaData', 'authentication-pages': 'authPages', 'otp_messages': 'authPages', 'registration-form': 'registrationForm', 'passwordless-login': 'passwordlessLoginSettings', 'checkout-verification': 'checkoutVerificationSettings', 'activity_hub': 'logsData', 'system_info': 'systemInfo', 'campaigns': 'campaignsData', 'campaigns_new': 'campaignsData', 'email_templates': 'emailTemplatesData', 'senders': 'sendersData', 'account': 'connector', 'abandoned_carts': 'abandonedCartSessions' }; const key = sectionToDataKey[section]; const hasInitialData = (section === 'dashboard' && enrichedData?.stats) || (key && enrichedData?.[key]); // Global Hub Logic: If we already have data and it's not a forced refresh, don't fetch. if (!force && hasInitialData) { setIsDataLoading(false); return; } isFetchingRef.current[sectionWithParams] = true; Promise.resolve().then(() => { setIsDataLoading(true); }); try { const gData = window.wawpDashboardData; const restBaseUrl = gData?.global?.restUrl; const wpRestNonce = gData?.global?.wpRestNonce; let success = false; const cacheBuster = `_=${Date.now()}`; if (restBaseUrl) { try { const separator = restBaseUrl.includes('?') ? '&' : '?'; const restUrl = `${restBaseUrl}/${section}${separator}${cacheBuster}`; const response = await fetch(restUrl, { method: 'GET', headers: { 'X-WP-Nonce': wpRestNonce || '', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }, credentials: 'same-origin' }); if (response.ok) { const result = await response.json(); setFetchedData(prev => { const next = { ...prev, ...result }; if (result.sidebarData && prev.sidebarData) { const prevSidebar = prev.sidebarData as SidebarData; const resultSidebar = result.sidebarData as SidebarData; next.sidebarData = { ...prevSidebar, ...resultSidebar, enabledSections: { ...(prevSidebar.enabledSections || {}), ...(resultSidebar.enabledSections || {}) }, enabledSenders: { ...(prevSidebar.enabledSenders || {}), ...(resultSidebar.enabledSenders || {}) } }; } const currentRawNotices = (rawData as DashboardData).notices; const prevNotices = Array.isArray(prev.notices) ? prev.notices : (Array.isArray(currentRawNotices) ? currentRawNotices : []); if (result.notices && Array.isArray(result.notices)) { const combinedNotices = [...prevNotices]; result.notices.forEach((newNotice: Notice) => { if (!combinedNotices.some(n => n.type === newNotice.type && n.url === newNotice.url)) { combinedNotices.push(newNotice); } }); next.notices = combinedNotices; } else if (!result.notices && prevNotices.length > 0) { next.notices = prevNotices; } // Update global window object to keep it in sync for non-React code or deep components if (window.wawpDashboardData) { window.wawpDashboardData = { ...window.wawpDashboardData, ...next } as DashboardData; } return next; }); success = true; } } catch (restErr) { console.warn(`[WAWP] REST Fetch failed for ${section}`, restErr); } } if (!success) { throw new Error(`Failed to fetch section data for ${section}`); } } catch (err) { console.error(`[WAWP] Critical fetch error for ${section}`, err); isFetchingRef.current[section] = false; } finally { setIsDataLoading(false); } }, [enrichedData, rawData]); useEffect(() => { const checkReady = () => { const data = window.wawpDashboardData; if (data) { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('token')) { urlParams.delete('token'); const cleanUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : ''); window.history.replaceState({}, '', cleanUrl); } const sectionFromUrl = urlParams.get('wawp_section'); const initialSection = sectionFromUrl || data.global?.section || (data as DashboardData).currentSection || 'dashboard'; setCurrentSection(initialSection); setReady(true); } else { setTimeout(checkReady, 100); } }; checkReady(); }, []); useEffect(() => { if (ready && currentSection) { const timer = setTimeout(() => { fetchSectionData(currentSection); }, 0); return () => clearTimeout(timer); } }, [currentSection, ready, fetchSectionData]); useEffect(() => { if (ready && currentSection) { const urlParams = new URLSearchParams(window.location.search); const highlight = urlParams.get('highlight_setting'); if (highlight) { const timer = setTimeout(() => { let element = (document.getElementById(highlight) || document.querySelector(`[name="${highlight}"]`) || document.querySelector(`[data-setting-key="${highlight}"]`)) as HTMLElement | null; if (!element) { const inputs = document.querySelectorAll('input, textarea, select'); for (const input of Array.from(inputs)) { const name = input.getAttribute('name') || ''; const id = input.getAttribute('id') || ''; if (name.includes(highlight) || id.includes(highlight) || name.replace(/_/g, '-').includes(highlight)) { element = (input.closest('.p-4') || input.closest('.space-y-4') || input.closest('div') || input) as HTMLElement; break; } } } if (!element) { const elements = document.querySelectorAll('label, span, h4, h5, div, p'); const targetWords = highlight.replace(/-/g, ' ').toLowerCase(); for (const el of Array.from(elements)) { const text = el.textContent?.toLowerCase() || ''; if (text.includes(targetWords) && el.children.length === 0) { element = (el.closest('.p-4') || el.closest('.space-y-4') || el.closest('.space-y-6') || el) as HTMLElement; break; } } } if (element) { const wrapper = (element.closest('.p-4') || element.closest('.space-y-4') || element) as HTMLElement; wrapper.classList.add('animate-highlight-glow'); wrapper.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { wrapper.classList.remove('animate-highlight-glow'); const cleanParams = new URLSearchParams(window.location.search); cleanParams.delete('highlight_setting'); const cleanUrl = window.location.pathname + (cleanParams.toString() ? '?' + cleanParams.toString() : ''); window.history.replaceState({}, '', cleanUrl); }, 3000); } }, 600); return () => clearTimeout(timer); } } }, [currentSection, ready]); const handleNavigation = useCallback((e: Event | CustomEvent) => { const section = (e as CustomEvent).detail; if (section) { // Allow re-navigation if it's a creation link or if it has parameters if (section === currentSection && !section.includes('&') && !section.includes('_new')) return; // Data Hub: We no longer delete the data when navigating. // This allows for instant back-and-forth transitions. isFetchingRef.current[section] = false; startTransition(() => { setCurrentSection(section); }); const url = new URL(window.location.href); // Clean up common parameters before setting new ones ['action', 'id', 'highlight_setting'].forEach(p => url.searchParams.delete(p)); if (section.includes('&')) { const [baseSection, ...rest] = section.split('&'); url.searchParams.set('wawp_section', baseSection); rest.forEach(pair => { const [k, v] = pair.split('='); if (k && v) url.searchParams.set(k, v); }); } else { url.searchParams.set('wawp_section', section); } window.history.pushState({}, '', url.toString()); } }, [currentSection]); useEffect(() => { const handlePopState = () => { const params = new URLSearchParams(window.location.search); const section = params.get('wawp_section') || 'dashboard'; startTransition(() => { setCurrentSection(section); }); }; const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (window.wawpIsDirty) { e.preventDefault(); e.returnValue = ''; } }; const handleRefresh = (e: Event | CustomEvent) => { const detail = (e as CustomEvent).detail; const section = (typeof detail === 'object' ? (detail as { section: string })?.section : detail) || currentSection; if (!section) return; isFetchingRef.current[section] = false; fetchSectionData(section, true); }; const handleSendersUpdate = (e: Event | CustomEvent>) => { const detail = (e as CustomEvent).detail; if (detail) { setFetchedData(prev => { const currentSidebar = (prev.sidebarData || (rawData as DashboardData).sidebarData) as SidebarData; return { ...prev, sidebarData: { ...currentSidebar, enabledSenders: { ...(currentSidebar.enabledSenders || {}), ...detail } } }; }); } }; const handleSectionsUpdate = (e: Event | CustomEvent>) => { const detail = (e as CustomEvent).detail; if (detail) { setFetchedData(prev => { const currentSidebar = (prev.sidebarData || (rawData as DashboardData).sidebarData) as SidebarData; return { ...prev, sidebarData: { ...currentSidebar, enabledSections: { ...(currentSidebar.enabledSections || {}), ...detail } } }; }); } }; window.addEventListener('beforeunload', handleBeforeUnload); window.addEventListener('wawp-navigate', handleNavigation); window.addEventListener('wawp-refresh-data', handleRefresh); window.addEventListener('wawp-senders-updated', handleSendersUpdate); window.addEventListener('wawp-sections-updated', handleSectionsUpdate); window.addEventListener('popstate', handlePopState); return () => { window.removeEventListener('beforeunload', handleBeforeUnload); window.removeEventListener('wawp-navigate', handleNavigation); window.removeEventListener('wawp-refresh-data', handleRefresh); window.removeEventListener('wawp-senders-updated', handleSendersUpdate); window.removeEventListener('wawp-sections-updated', handleSectionsUpdate); window.removeEventListener('popstate', handlePopState); }; }, [handleNavigation, currentSection, fetchSectionData, rawData]); if (!ready || !enrichedData) return null; const renderContent = () => { const sectionToDataKey: Record = { 'whatsapp-web-sender': 'instancesData', 'meta-api-sender': 'metaApiSenderData', 'firebase-sender': 'firebaseSenderData', 'smtp-sender': 'smtpSenderData', 'block-manager': 'blockManagerData', 'notifications': 'notificationsData', 'chat_widget': 'chatWidgetData', 'country-code': 'phoneFieldData', 'abandoned_carts_settings': 'abandonedCartsSettings', 'google_recaptcha': 'recaptchaData', 'authentication-pages': 'authPages', 'registration-form': 'registrationForm', 'passwordless-login': 'passwordlessLoginSettings', 'abandoned_carts': 'abandonedCartSessions', 'activity_hub': 'logsData', 'system_info': 'systemInfo', 'campaigns': 'campaignsData', 'campaigns_new': 'campaignsData', 'email_templates': 'emailTemplatesData', 'senders': 'sendersData', 'account': 'connector' }; const key = sectionToDataKey[currentSection]; if (isDataLoading && key && !enrichedData[key]) { return ; } const isLogSection = ['activity_hub'].includes(currentSection); const componentsMap: Record> = { 'dashboard': Dashboard, 'system_info': SystemInfo, 'account': Connector, 'notifications': NotificationsBuilder, 'email_templates': EmailTemplates, 'campaigns': Campaigns, 'campaigns_new': Campaigns, 'chat_widget': ChatWidget, 'country-code': PhoneFieldSettings, 'google_recaptcha': GoogleRecaptchaSettings, 'authentication-pages': AuthenticationPagesSettings, 'checkout-verification': CheckoutVerificationSettings, 'registration-form': RegistrationFormSettings, 'passwordless-login': PasswordlessLoginSettings, 'otp_messages': AuthenticationPagesSettings, 'abandoned_carts_settings': AbandonedCartSettings, 'abandoned_carts': AbandonedCartSessions, 'senders': Senders, 'whatsapp-web-sender': WhatsAppWebSender, 'meta-api-sender': MetaAPISender, 'firebase-sender': FirebaseSender, 'smtp-sender': SMTPSender, 'block-manager': BlockManager, 'activity_hub': ActivityLogs }; const [baseSection] = currentSection.split('&'); const TargetComponent = componentsMap[currentSection] || componentsMap[baseSection] || (isLogSection ? ActivityLogs : Dashboard); return ( ); } return ( {isPending &&
} }> {renderContent()} ) }