## ✅ Issue 1: Single Source of Truth for Navigation **Problem:** Confusing dual nav sources (PHP + TypeScript fallback) **Solution:** Removed static TypeScript fallback tree **Result:** PHP NavigationRegistry is now the ONLY source - More flexible (can check WooCommerce settings, extend via addons) - Easier to maintain - Clear error if backend data missing ## ✅ Issue 2: Logo in All Modes **Already Working:** Header component renders in all modes - Standalone ✅ - WP-Admin normal ✅ - WP-Admin fullscreen ✅ ## ✅ Issue 5: Customer Settings 404 Debug **Added:** Debug logging to track endpoint calls **Note:** Routes are correctly registered - May need WordPress permalinks flush - Check debug.log for errors ## ✅ Issue 6: Dark Mode Logo Support **Implemented:** 1. **Backend:** - Added `store_logo_dark` to branding endpoint - Returns both light and dark logos 2. **Header Component:** - Detects dark mode via MutationObserver - Switches logo based on theme - Falls back to light logo if dark not set 3. **Login Screen:** - Same dark mode detection - Theme-aware logo display - Seamless theme switching 4. **SVG Support:** - Already supported via `accept="image/*"` - Works for all image formats **Result:** Perfect dark/light logo switching everywhere! 🌓 --- ## Files Modified: - `nav/tree.ts` - Removed static fallback - `App.tsx` - Dark logo in header - `Login.tsx` - Dark logo in login - `StoreController.php` - Dark logo in branding endpoint + debug logs - `Store.tsx` - Already has dark logo upload field - `StoreSettingsProvider.php` - Already has dark logo backend ## Testing: 1. Upload dark logo in Store settings 2. Switch theme - logo should change 3. Check customer-settings endpoint in browser console 4. Verify nav items from PHP only
197 lines
6.4 KiB
TypeScript
197 lines
6.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { Loader2, ArrowLeft } from 'lucide-react';
|
|
import { __ } from '@/lib/i18n';
|
|
|
|
export function Login() {
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [branding, setBranding] = React.useState({
|
|
logo: '',
|
|
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')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
setBranding({
|
|
logo: data.store_logo || '',
|
|
logoDark: data.store_logo_dark || '',
|
|
storeName: data.store_name || 'WooNooW',
|
|
});
|
|
})
|
|
.catch(err => console.error('Failed to load branding:', err));
|
|
}, []);
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setIsLoading(true);
|
|
setError('');
|
|
|
|
try {
|
|
const response = await fetch((window.WNW_CONFIG?.restUrl || '') + '/auth/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
// CRITICAL: Reload page to get fresh cookies and nonce from PHP
|
|
// wp_signon sets cookies but we need PHP to render with proper auth state
|
|
window.location.href = window.location.origin + window.location.pathname;
|
|
} else {
|
|
setError(data.message || __('Invalid username or password'));
|
|
}
|
|
} catch (err) {
|
|
console.error('Login error:', err);
|
|
setError(__('Login failed. Please try again.'));
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 p-4">
|
|
<div className="w-full max-w-md">
|
|
<div className="bg-white dark:bg-gray-900/50 dark:backdrop-blur-xl dark:border dark:border-gray-800 rounded-lg shadow-xl p-8">
|
|
{/* Logo */}
|
|
<div className="text-center mb-8">
|
|
{(isDark && branding.logoDark) || branding.logo ? (
|
|
<img
|
|
src={(isDark && branding.logoDark) || branding.logo}
|
|
alt={branding.storeName}
|
|
className="h-16 mx-auto mb-3 object-contain"
|
|
/>
|
|
) : (
|
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
|
{branding.storeName}
|
|
</h1>
|
|
)}
|
|
{branding.tagline && (
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
|
{branding.tagline}
|
|
</p>
|
|
)}
|
|
<p className="text-gray-600 dark:text-gray-400">
|
|
{__('Sign in to your admin dashboard')}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Error Alert */}
|
|
{error && (
|
|
<Alert variant="destructive" className="mb-6">
|
|
<AlertDescription>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Login Form */}
|
|
<form onSubmit={handleLogin} className="space-y-6">
|
|
<div>
|
|
<Label htmlFor="username">{__('Username or Email')}</Label>
|
|
<Input
|
|
id="username"
|
|
type="text"
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
placeholder={__('Enter your username')}
|
|
required
|
|
disabled={isLoading}
|
|
className="mt-1"
|
|
autoComplete="username"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="password">{__('Password')}</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder={__('Enter your password')}
|
|
required
|
|
disabled={isLoading}
|
|
className="mt-1"
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
type="submit"
|
|
className="w-full"
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
{__('Signing in...')}
|
|
</>
|
|
) : (
|
|
__('Sign In')
|
|
)}
|
|
</Button>
|
|
</form>
|
|
|
|
{/* Footer Links */}
|
|
<div className="mt-6 space-y-3">
|
|
<a
|
|
href={window.WNW_CONFIG?.wpAdminUrl || '/wp-admin'}
|
|
className="flex items-center justify-center gap-2 text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
{__('Back to WordPress Admin')}
|
|
</a>
|
|
|
|
<div className="text-center">
|
|
<a
|
|
href={(window.WNW_CONFIG?.siteUrl || '') + '/wp-login.php?action=lostpassword'}
|
|
className="text-sm text-gray-600 hover:text-gray-700 dark:text-gray-400 transition-colors"
|
|
>
|
|
{__('Forgot password?')}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Site Info */}
|
|
<div className="text-center mt-6 text-sm text-gray-600 dark:text-gray-400">
|
|
{branding.storeName}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|