diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index f6bef5e..d2d0e98 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -272,9 +272,30 @@ function AddonRoute({ config }: { config: any }) { function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRef, onVisibilityChange }: { onFullscreen: () => void; fullscreen: boolean; showToggle?: boolean; scrollContainerRef?: React.RefObject; onVisibilityChange?: (visible: boolean) => void }) { const [siteTitle, setSiteTitle] = React.useState((window as any).wnw?.siteTitle || 'WooNooW'); const [storeLogo, setStoreLogo] = React.useState(''); + const [storeLogoDark, setStoreLogoDark] = React.useState(''); const [isVisible, setIsVisible] = React.useState(true); const lastScrollYRef = React.useRef(0); const isStandalone = window.WNW_CONFIG?.standaloneMode ?? false; + const [isDark, setIsDark] = React.useState(false); + + // Detect dark mode + React.useEffect(() => { + const checkDarkMode = () => { + const htmlEl = document.documentElement; + setIsDark(htmlEl.classList.contains('dark')); + }; + + checkDarkMode(); + + // Watch for theme changes + const observer = new MutationObserver(checkDarkMode); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }); + + return () => observer.disconnect(); + }, []); // Notify parent of visibility changes React.useEffect(() => { @@ -289,6 +310,7 @@ function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRe if (response.ok) { const data = await response.json(); if (data.store_logo) setStoreLogo(data.store_logo); + if (data.store_logo_dark) setStoreLogoDark(data.store_logo_dark); if (data.store_name) setSiteTitle(data.store_name); } } catch (err) { @@ -302,6 +324,7 @@ function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRe React.useEffect(() => { const handleStoreUpdate = (event: CustomEvent) => { if (event.detail?.store_logo) setStoreLogo(event.detail.store_logo); + if (event.detail?.store_logo_dark) setStoreLogoDark(event.detail.store_logo_dark); if (event.detail?.store_name) setSiteTitle(event.detail.store_name); }; @@ -358,11 +381,14 @@ function Header({ onFullscreen, fullscreen, showToggle = true, scrollContainerRe return null; } + // Choose logo based on theme + const currentLogo = isDark && storeLogoDark ? storeLogoDark : storeLogo; + return (
- {storeLogo ? ( - {siteTitle} + {currentLogo ? ( + {siteTitle} ) : (
{siteTitle}
)} diff --git a/admin-spa/src/nav/tree.ts b/admin-spa/src/nav/tree.ts index 804bfaa..bff352e 100644 --- a/admin-spa/src/nav/tree.ts +++ b/admin-spa/src/nav/tree.ts @@ -19,102 +19,18 @@ export type MainNode = { }; /** - * Get navigation tree from backend (dynamic) - * Falls back to static tree if backend data not available + * Get navigation tree from backend (SINGLE SOURCE OF TRUTH) + * Backend (PHP) is more flexible - can check WooCommerce settings, be extended by addons, etc. */ function getNavTreeFromBackend(): MainNode[] { const backendTree = (window as any).WNW_NAV_TREE; - if (Array.isArray(backendTree) && backendTree.length > 0) { - return backendTree; + if (!Array.isArray(backendTree) || backendTree.length === 0) { + console.error('WNW_NAV_TREE not found! Navigation will be empty.'); + return []; } - // Fallback to static tree (for development/safety) - return getStaticFallbackTree(); -} - -/** - * Static fallback tree (used if backend data not available) - */ -function getStaticFallbackTree(): MainNode[] { - const admin = - (window as any).wnw?.adminUrl ?? - (window as any).woonoow?.adminUrl ?? - '/wp-admin/admin.php'; - - return [ - { - key: 'dashboard', - label: 'Dashboard', - path: '/dashboard', - icon: 'layout-dashboard', - children: [ - { label: 'Overview', mode: 'spa', path: '/dashboard', exact: true }, - { label: 'Revenue', mode: 'spa', path: '/dashboard/revenue' }, - { label: 'Orders', mode: 'spa', path: '/dashboard/orders' }, - { label: 'Products', mode: 'spa', path: '/dashboard/products' }, - { label: 'Customers', mode: 'spa', path: '/dashboard/customers' }, - { label: 'Coupons', mode: 'spa', path: '/dashboard/coupons' }, - { label: 'Taxes', mode: 'spa', path: '/dashboard/taxes' }, - ], - }, - { - key: 'orders', - label: 'Orders', - path: '/orders', - icon: 'receipt-text', - children: [], - }, - { - key: 'products', - label: 'Products', - path: '/products', - icon: 'package', - children: [ - { label: 'All products', mode: 'spa', path: '/products' }, - { label: 'New', mode: 'spa', path: '/products/new' }, - { label: 'Categories', mode: 'spa', path: '/products/categories' }, - { label: 'Tags', mode: 'spa', path: '/products/tags' }, - { label: 'Attributes', mode: 'spa', path: '/products/attributes' }, - ], - }, - { - key: 'coupons', - label: 'Coupons', - path: '/coupons', - icon: 'tag', - children: [ - { label: 'All coupons', mode: 'spa', path: '/coupons' }, - { label: 'New', mode: 'spa', path: '/coupons/new' }, - ], - }, - { - key: 'customers', - label: 'Customers', - path: '/customers', - icon: 'users', - children: [ - { label: 'All customers', mode: 'spa', path: '/customers' }, - ], - }, - { - key: 'settings', - label: 'Settings', - path: '/settings/store', - icon: 'settings', - // Settings submenu available in all modes for consistent experience - children: [ - // Core Settings (Shopify-inspired) - { label: 'Store Details', mode: 'spa' as const, path: '/settings/store' }, - { label: 'Payments', mode: 'spa' as const, path: '/settings/payments' }, - { label: 'Shipping & Delivery', mode: 'spa' as const, path: '/settings/shipping' }, - { label: 'Tax', mode: 'spa' as const, path: '/settings/tax' }, - { label: 'Customers', mode: 'spa' as const, path: '/settings/customers' }, - { label: 'Notifications', mode: 'spa' as const, path: '/settings/notifications' }, - { label: 'Developer', mode: 'spa' as const, path: '/settings/developer' }, - ], - }, - ]; + return backendTree; } /** diff --git a/admin-spa/src/routes/Login.tsx b/admin-spa/src/routes/Login.tsx index 86b35f5..ef62afa 100644 --- a/admin-spa/src/routes/Login.tsx +++ b/admin-spa/src/routes/Login.tsx @@ -12,13 +12,31 @@ export function Login() { const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); - const [branding, setBranding] = useState<{ logo: string; tagline: string; storeName: string }>({ + const [branding, setBranding] = React.useState({ logo: '', - tagline: '', - storeName: 'WooNooW' + logoDark: '', + storeName: 'WooNooW', }); + const [isDark, setIsDark] = React.useState(false); const navigate = useNavigate(); + // Detect dark mode + React.useEffect(() => { + const checkDarkMode = () => { + setIsDark(document.documentElement.classList.contains('dark')); + }; + + checkDarkMode(); + + const observer = new MutationObserver(checkDarkMode); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }); + + return () => observer.disconnect(); + }, []); + // Fetch branding (public endpoint - no auth required) useEffect(() => { fetch((window.WNW_CONFIG?.restUrl || '') + '/store/branding') @@ -26,8 +44,8 @@ export function Login() { .then(data => { setBranding({ logo: data.store_logo || '', - tagline: data.store_tagline || '', - storeName: data.store_name || 'WooNooW' + logoDark: data.store_logo_dark || '', + storeName: data.store_name || 'WooNooW', }); }) .catch(err => console.error('Failed to load branding:', err)); @@ -71,9 +89,9 @@ export function Login() {
{/* Logo */}
- {branding.logo ? ( + {(isDark && branding.logoDark) || branding.logo ? ( {branding.storeName} diff --git a/includes/Api/StoreController.php b/includes/Api/StoreController.php index 162aaf7..3b22a1f 100644 --- a/includes/Api/StoreController.php +++ b/includes/Api/StoreController.php @@ -114,8 +114,9 @@ class StoreController extends WP_REST_Controller { */ public function get_branding(WP_REST_Request $request) { $branding = [ - 'store_name' => get_option('blogname', 'WooNooW'), + 'store_name' => get_option('woonoow_store_name', '') ?: get_option('blogname', 'WooNooW'), 'store_logo' => get_option('woonoow_store_logo', ''), + 'store_logo_dark' => get_option('woonoow_store_logo_dark', ''), 'store_icon' => get_option('woonoow_store_icon', ''), 'store_tagline' => get_option('woonoow_store_tagline', ''), ]; @@ -267,14 +268,17 @@ class StoreController extends WP_REST_Controller { * @return WP_REST_Response|WP_Error Response object or error */ public function get_customer_settings(WP_REST_Request $request) { + error_log('WooNooW: get_customer_settings called'); try { $settings = CustomerSettingsProvider::get_settings(); + error_log('WooNooW: Customer settings retrieved: ' . print_r($settings, true)); $response = rest_ensure_response($settings); $response->header('Cache-Control', 'max-age=60'); return $response; } catch (\Exception $e) { + error_log('WooNooW: get_customer_settings exception: ' . $e->getMessage()); return new WP_Error( 'get_customer_settings_failed', $e->getMessage(),