untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))), 'nonce' => wp_create_nonce('wp_rest'), 'isDev' => true, 'devServer' => $dev_url, 'adminScreen' => 'woonoow', 'adminUrl' => admin_url('admin.php'), ]); wp_add_inline_script($handle, 'window.WNW_API = window.WNW_API || WNW_API;', 'after'); // WNW_CONFIG for compatibility with standalone mode code wp_localize_script($handle, 'WNW_CONFIG', [ 'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))), 'nonce' => wp_create_nonce('wp_rest'), 'standaloneMode' => false, 'wpAdminUrl' => admin_url('admin.php?page=woonoow'), 'isAuthenticated' => is_user_logged_in(), 'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))), ]); wp_add_inline_script($handle, 'window.WNW_CONFIG = window.WNW_CONFIG || WNW_CONFIG;', 'after'); // WordPress REST API settings (for media upload compatibility) wp_localize_script($handle, 'wpApiSettings', [ 'root' => untrailingslashit(esc_url_raw(rest_url())), 'nonce' => wp_create_nonce('wp_rest'), ]); wp_add_inline_script($handle, 'window.wpApiSettings = window.wpApiSettings || wpApiSettings;', 'after'); // Also expose compact global for convenience wp_localize_script($handle, 'wnw', [ 'isDev' => true, 'devServer' => $dev_url, 'adminUrl' => admin_url('admin.php'), 'siteTitle' => get_bloginfo('name') ?: 'WooNooW', ]); wp_add_inline_script($handle, 'window.wnw = window.wnw || wnw;', 'after'); // Localize store currency data (same as prod) wp_localize_script($handle, 'WNW_STORE', self::store_runtime()); wp_add_inline_script($handle, 'window.WNW_STORE = window.WNW_STORE || WNW_STORE;', 'after'); // Localize Woo menus snapshot for instant render $menus_snapshot = class_exists(MenuProvider::class) ? MenuProvider::get_snapshot() : []; wp_localize_script($handle, 'WNW_WC_MENUS', ['items' => $menus_snapshot]); wp_add_inline_script($handle, 'window.WNW_WC_MENUS = window.WNW_WC_MENUS || WNW_WC_MENUS;', 'after'); // Addon system data wp_localize_script($handle, 'WNW_ADDONS', AddonRegistry::get_frontend_registry()); wp_add_inline_script($handle, 'window.WNW_ADDONS = window.WNW_ADDONS || WNW_ADDONS;', 'after'); wp_localize_script($handle, 'WNW_ADDON_ROUTES', RouteRegistry::get_frontend_routes()); wp_add_inline_script($handle, 'window.WNW_ADDON_ROUTES = window.WNW_ADDON_ROUTES || WNW_ADDON_ROUTES;', 'after'); wp_localize_script($handle, 'WNW_NAV_TREE', NavigationRegistry::get_frontend_nav_tree()); wp_add_inline_script($handle, 'window.WNW_NAV_TREE = window.WNW_NAV_TREE || WNW_NAV_TREE;', 'after'); // Temporary compat aliases for old WNM_* wp_add_inline_script($handle, 'window.WNM_API = window.WNM_API || window.WNW_API;', 'after'); wp_add_inline_script($handle, 'window.WNM_WC_MENUS = window.WNM_WC_MENUS || window.WNW_WC_MENUS;', 'after'); // 2) Print a real module tag in the footer to load Vite client + app add_action('admin_print_footer_scripts', function () use ($dev_url) { // 1) React Refresh preamble (required by @vitejs/plugin-react) ?> ' . "\n", esc_url($dev_url)); // 3) Your app entry printf('' . "\n", esc_url($dev_url)); }, 1); } /** ---------------------------------------- * PROD MODE (built assets in admin-spa/dist) * -------------------------------------- */ private static function enqueue_prod(): void { // Get plugin root directory (2 levels up from includes/Admin/) $plugin_dir = dirname(dirname(__DIR__)); $dist_dir = $plugin_dir . '/admin-spa/dist/'; $base_url = plugins_url('admin-spa/dist/', $plugin_dir . '/woonoow.php'); $css = 'app.css'; $js = 'app.js'; $ver_css = file_exists($dist_dir . $css) ? (string) filemtime($dist_dir . $css) : self::asset_version(); $ver_js = file_exists($dist_dir . $js) ? (string) filemtime($dist_dir . $js) : self::asset_version(); // Debug logging if (defined('WP_DEBUG') && WP_DEBUG) { error_log('[WooNooW Assets] Dist dir: ' . $dist_dir); error_log('[WooNooW Assets] CSS exists: ' . (file_exists($dist_dir . $css) ? 'yes' : 'no')); error_log('[WooNooW Assets] JS exists: ' . (file_exists($dist_dir . $js) ? 'yes' : 'no')); error_log('[WooNooW Assets] CSS URL: ' . $base_url . $css); error_log('[WooNooW Assets] JS URL: ' . $base_url . $js); } if (file_exists($dist_dir . $css)) { wp_enqueue_style('wnw-admin', $base_url . $css, [], $ver_css); } if (file_exists($dist_dir . $js)) { wp_enqueue_script('wnw-admin', $base_url . $js, ['wp-element'], $ver_js, true); self::localize_runtime('wnw-admin'); } } /** Attach runtime config to a handle */ private static function localize_runtime(string $handle): void { wp_localize_script($handle, 'WNW_API', [ 'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))), 'nonce' => wp_create_nonce('wp_rest'), 'isDev' => self::is_dev_mode(), 'devServer' => self::dev_server_url(), 'adminScreen' => 'woonoow', 'adminUrl' => admin_url('admin.php'), ]); // WNW_CONFIG for compatibility with standalone mode code wp_localize_script($handle, 'WNW_CONFIG', [ 'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))), 'nonce' => wp_create_nonce('wp_rest'), 'standaloneMode' => false, 'wpAdminUrl' => admin_url('admin.php?page=woonoow'), 'isAuthenticated' => is_user_logged_in(), 'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))), ]); // WordPress REST API settings (for media upload compatibility) wp_localize_script($handle, 'wpApiSettings', [ 'root' => untrailingslashit(esc_url_raw(rest_url())), 'nonce' => wp_create_nonce('wp_rest'), ]); wp_localize_script($handle, 'WNW_STORE', self::store_runtime()); wp_add_inline_script($handle, 'window.WNW_STORE = window.WNW_STORE || WNW_STORE;', 'after'); // Compact global (prod) wp_localize_script($handle, 'wnw', [ 'isDev' => (bool) self::is_dev_mode(), 'devServer' => (string) self::dev_server_url(), 'adminUrl' => admin_url('admin.php'), 'siteTitle' => get_bloginfo('name') ?: 'WooNooW', ]); wp_add_inline_script($handle, 'window.wnw = window.wnw || wnw;', 'after'); // Menus snapshot (prod) $menus_snapshot = class_exists(MenuProvider::class) ? MenuProvider::get_snapshot() : []; wp_localize_script($handle, 'WNW_WC_MENUS', ['items' => $menus_snapshot]); wp_add_inline_script($handle, 'window.WNW_WC_MENUS = window.WNW_WC_MENUS || WNW_WC_MENUS;', 'after'); // Addon system data (prod) wp_localize_script($handle, 'WNW_ADDONS', AddonRegistry::get_frontend_registry()); wp_add_inline_script($handle, 'window.WNW_ADDONS = window.WNW_ADDONS || WNW_ADDONS;', 'after'); wp_localize_script($handle, 'WNW_ADDON_ROUTES', RouteRegistry::get_frontend_routes()); wp_add_inline_script($handle, 'window.WNW_ADDON_ROUTES = window.WNW_ADDON_ROUTES || WNW_ADDON_ROUTES;', 'after'); wp_localize_script($handle, 'WNW_NAV_TREE', NavigationRegistry::get_frontend_nav_tree()); wp_add_inline_script($handle, 'window.WNW_NAV_TREE = window.WNW_NAV_TREE || WNW_NAV_TREE;', 'after'); // Temporary compat aliases for old WNM_* wp_add_inline_script($handle, 'window.WNM_API = window.WNM_API || window.WNW_API;', 'after'); wp_add_inline_script($handle, 'window.WNM_WC_MENUS = window.WNM_WC_MENUS || window.WNW_WC_MENUS;', 'after'); } /** Runtime store meta for frontend (currency, decimals, separators, position). */ private static function store_runtime(): array { // WooCommerce helpers may not exist in some contexts; guard with defaults $currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD'; $currency_sym = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol($currency) : '$'; $decimals = function_exists('wc_get_price_decimals') ? wc_get_price_decimals() : 2; $thousand_sep = function_exists('wc_get_price_thousand_separator') ? wc_get_price_thousand_separator() : ','; $decimal_sep = function_exists('wc_get_price_decimal_separator') ? wc_get_price_decimal_separator() : '.'; $currency_pos = function_exists('get_option') ? get_option('woocommerce_currency_pos', 'left') : 'left'; return [ 'currency' => $currency, 'currency_symbol' => $currency_sym, 'decimals' => (int) $decimals, 'thousand_sep' => (string) $thousand_sep, 'decimal_sep' => (string) $decimal_sep, 'currency_pos' => (string) $currency_pos, ]; } /** Determine dev mode: * - ONLY enabled if constant WOONOOW_ADMIN_DEV=true is explicitly set * - or filter override (woonoow/admin_is_dev) * * Note: We don't check WP_ENV to avoid accidentally enabling dev mode * in Local by Flywheel or other local dev environments. */ private static function is_dev_mode(): bool { // Only enable dev mode if explicitly set via constant $const_dev = defined('WOONOOW_ADMIN_DEV') && WOONOOW_ADMIN_DEV === true; /** * Filter: force dev/prod mode for WooNooW admin assets. * Return true to use Vite dev server, false to use built assets. * * IMPORTANT: This filter should NOT be used in production! * Only use it during development. */ $filtered = apply_filters('woonoow/admin_is_dev', $const_dev); // Debug logging (only if WP_DEBUG is enabled) if (defined('WP_DEBUG') && WP_DEBUG && $filtered !== $const_dev) { error_log('[WooNooW Assets] Dev mode changed by filter: ' . ($filtered ? 'true' : 'false')); } return (bool) $filtered; } /** Dev server URL (filterable) */ private static function dev_server_url(): string { $default = 'http://localhost:5173'; /** Filter: change dev server URL if needed */ return (string) apply_filters('woonoow/admin_dev_server', $default); } /** Basic asset versioning */ private static function asset_version(): string { // Bump when releasing; in dev we don't cache-bust return defined('WOONOOW_VERSION') ? WOONOOW_VERSION : '0.1.0'; } }