Files
WooNooW/admin-spa/src/routes/Login.tsx
dwindown 432d84992c fix: Single source nav + dark logo support + customer settings debug
##  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
2025-11-11 10:12:30 +07:00

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>
);
}