From 30f2fc2ea670d5b4f9c91e1991ba3484a8c6ce5c Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Mon, 1 Jun 2026 00:58:18 +0700 Subject: [PATCH] fix(spa-nav): refresh navigation immediately when modules are toggled --- admin-spa/src/hooks/useActiveSection.ts | 22 +++++++++++++++++----- admin-spa/src/routes/Settings/Modules.tsx | 14 +++++++++++++- admin-spa/src/types/window.d.ts | 23 ++++++++++++++++------- includes/Api/ModulesController.php | 4 ++++ includes/Compat/NavigationRegistry.php | 13 +++++++++---- 5 files changed, 59 insertions(+), 17 deletions(-) diff --git a/admin-spa/src/hooks/useActiveSection.ts b/admin-spa/src/hooks/useActiveSection.ts index b49a545..c743128 100644 --- a/admin-spa/src/hooks/useActiveSection.ts +++ b/admin-spa/src/hooks/useActiveSection.ts @@ -1,8 +1,23 @@ import { useLocation } from 'react-router-dom'; -import { navTree, MainNode, NAV_TREE_VERSION } from '../nav/tree'; +import { useEffect, useState } from 'react'; +import { navTree as initialNavTree, MainNode } from '../nav/tree'; + +function getCurrentNavTree(): MainNode[] { + const tree = window.WNW_NAV_TREE; + return Array.isArray(tree) && tree.length > 0 + ? (tree as MainNode[]) + : initialNavTree; +} export function useActiveSection(): { main: MainNode; all: MainNode[] } { const { pathname } = useLocation(); + const [navTree, setNavTree] = useState(getCurrentNavTree); + + useEffect(() => { + const handleNavUpdate = () => setNavTree(getCurrentNavTree()); + window.addEventListener('woonoow:navigation-updated', handleNavUpdate); + return () => window.removeEventListener('woonoow:navigation-updated', handleNavUpdate); + }, []); function pick(): MainNode { // Special case: /settings should match settings section @@ -32,8 +47,5 @@ export function useActiveSection(): { main: MainNode; all: MainNode[] } { const main = pick(); const children = Array.isArray(main.children) ? main.children : []; - // Debug: ensure we are using the latest tree module (driven by PHP-localized window.wnw.isDev) - const isDev = Boolean((window as any).wnw?.isDev); - return { main: { ...main, children }, all: navTree } as const; -} \ No newline at end of file +} diff --git a/admin-spa/src/routes/Settings/Modules.tsx b/admin-spa/src/routes/Settings/Modules.tsx index cc552a5..2016192 100644 --- a/admin-spa/src/routes/Settings/Modules.tsx +++ b/admin-spa/src/routes/Settings/Modules.tsx @@ -51,8 +51,20 @@ export default function Modules() { mutationFn: async ({ moduleId, enabled }: { moduleId: string; enabled: boolean }) => { return api.post('/modules/toggle', { module_id: moduleId, enabled }); }, - onSuccess: (data, variables) => { + onSuccess: (data: any, variables) => { + if (Array.isArray(data?.enabled_modules)) { + queryClient.setQueryData(['modules-enabled'], { enabled: data.enabled_modules }); + } + + if (Array.isArray(data?.nav_tree)) { + window.WNW_NAV_TREE = data.nav_tree; + window.dispatchEvent(new CustomEvent('woonoow:navigation-updated', { + detail: { moduleId: variables.moduleId, enabled: variables.enabled }, + })); + } + queryClient.invalidateQueries({ queryKey: ['modules'] }); + queryClient.invalidateQueries({ queryKey: ['modules-enabled'] }); toast.success( variables.enabled ? __('Module enabled successfully') diff --git a/admin-spa/src/types/window.d.ts b/admin-spa/src/types/window.d.ts index 86b291b..5dcbb56 100644 --- a/admin-spa/src/types/window.d.ts +++ b/admin-spa/src/types/window.d.ts @@ -60,6 +60,14 @@ interface WNW_Store { position?: string; } +interface WNW_NavNode { + key: string; + path: string; + icon: string; + label: string; + children?: any[]; +} + declare global { interface Window { WNW_API: WNW_API_Config; // Make required to avoid "possibly undefined" check in every usage if we are sure it exists @@ -67,19 +75,20 @@ declare global { WNW_WC_MENUS?: WNW_WC_MENUS; WNW_CONFIG?: WNW_CONFIG; WNW_STORE?: WNW_Store; - WNW_NAV_TREE?: Array<{ - key: string; - path: string; - icon: string; - label: string; - children?: any[]; - }>; + WNW_NAV_TREE?: WNW_NavNode[]; WNW_ADDON_ROUTES?: Array<{ path: string; component_url: string; props?: Record; }>; } + + interface WindowEventMap { + 'woonoow:navigation-updated': CustomEvent<{ + moduleId?: string; + enabled?: boolean; + }>; + } } export { }; diff --git a/includes/Api/ModulesController.php b/includes/Api/ModulesController.php index fa6f0ea..0d39eb5 100644 --- a/includes/Api/ModulesController.php +++ b/includes/Api/ModulesController.php @@ -151,6 +151,10 @@ class ModulesController extends WP_REST_Controller { 'success' => true, 'module_id' => $module_id, 'enabled' => $enabled, + 'enabled_modules' => ModuleRegistry::get_enabled_modules(), + 'nav_tree' => class_exists('\WooNooW\Compat\NavigationRegistry') + ? \WooNooW\Compat\NavigationRegistry::get_frontend_nav_tree() + : [], ]); } diff --git a/includes/Compat/NavigationRegistry.php b/includes/Compat/NavigationRegistry.php index adfd9cf..86f1922 100644 --- a/includes/Compat/NavigationRegistry.php +++ b/includes/Compat/NavigationRegistry.php @@ -15,7 +15,7 @@ if (! defined('ABSPATH')) exit; class NavigationRegistry { const NAV_OPTION = 'wnw_nav_tree'; - const NAV_VERSION = '1.3.1'; // Updated Coupons link + const NAV_VERSION = '1.3.3'; // Reordered marketing links by usage priority /** * Initialize hooks @@ -217,14 +217,19 @@ class NavigationRegistry { $children = []; + // Coupons - always available + $children[] = ['label' => __('Coupons', 'woonoow'), 'mode' => 'spa', 'path' => '/marketing/coupons']; + + // Affiliate - only if module enabled + if (\WooNooW\Core\ModuleRegistry::is_enabled('affiliate')) { + $children[] = ['label' => __('Affiliates', 'woonoow'), 'mode' => 'spa', 'path' => '/marketing/affiliates']; + } + // Newsletter - only if module enabled if (\WooNooW\Core\ModuleRegistry::is_enabled('newsletter')) { $children[] = ['label' => __('Newsletter', 'woonoow'), 'mode' => 'spa', 'path' => '/marketing/newsletter']; } - // Coupons - always available - $children[] = ['label' => __('Coupons', 'woonoow'), 'mode' => 'spa', 'path' => '/marketing/coupons']; - return $children; }