feat: Public guest wishlist page + Dashboard Overview debug

Issue 1 - Dashboard > Overview Never Active:
Added debug logging to investigate why Overview submenu never shows active
- Console logs path, pathname, and isActive state
- Will help identify the root cause

Issue 2 - Guest Wishlist Public Page:
Problem: Guests couldn't access wishlist (redirected to login)
Solution: Created public /wishlist route accessible to all users

Implementation:
1. New Public Wishlist Page:
   - Route: /wishlist (not /my-account/wishlist)
   - Accessible to guests and logged-in users
   - Guest mode: Shows product IDs from localStorage
   - Logged-in mode: Shows full product details from API
   - Guests can view and remove items

2. Updated All Header Links:
   - ClassicLayout: /wishlist
   - ModernLayout: /wishlist
   - BoutiqueLayout: /wishlist
   - No more wp-login redirect for guests

3. Guest Experience:
   - See list of wishlisted product IDs
   - Click to view product details
   - Remove items from wishlist
   - Prompt to login for full details

Issue 3 - Wishlist Page Selector Setting:
Status: Deprecated/unused for SPA architecture
- SPA uses React Router, not WordPress pages
- Setting saved but has no effect
- Shareable wishlist would also be SPA route
- No need for page CPT selection

Files Modified:
- customer-spa/src/pages/Wishlist.tsx (new public page)
- customer-spa/src/App.tsx (added /wishlist route)
- customer-spa/src/hooks/useWishlist.ts (export productIds)
- customer-spa/src/layouts/BaseLayout.tsx (all themes use /wishlist)
- customer-spa/dist/app.js (rebuilt)
- admin-spa/src/components/nav/SubmenuBar.tsx (debug logging)
- admin-spa/dist/app.js (rebuilt)

Result:
 Guests can access wishlist page
 Guests can view and manage localStorage wishlist
 No login redirect for guest wishlist
 Debug logging added for Overview issue
This commit is contained in:
Dwindi Ramadhana
2025-12-26 23:16:40 +07:00
parent e64045b0e1
commit 9214172c79
5 changed files with 181 additions and 3 deletions

View File

@@ -30,6 +30,11 @@ export default function SubmenuBar({ items = [], fullscreen = false, headerVisib
// Only ONE submenu item should be active at a time // Only ONE submenu item should be active at a time
const isActive = it.path === pathname; const isActive = it.path === pathname;
// Debug logging for Dashboard Overview issue
if (it.label === 'Overview' && pathname.includes('dashboard')) {
console.log('Overview check:', { label: it.label, path: it.path, pathname, isActive });
}
const cls = [ const cls = [
'ui-ctrl inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 border text-sm whitespace-nowrap', 'ui-ctrl inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 border text-sm whitespace-nowrap',
'focus:outline-none focus:ring-0 focus:shadow-none', 'focus:outline-none focus:ring-0 focus:shadow-none',

View File

@@ -14,6 +14,7 @@ import Cart from './pages/Cart';
import Checkout from './pages/Checkout'; import Checkout from './pages/Checkout';
import ThankYou from './pages/ThankYou'; import ThankYou from './pages/ThankYou';
import Account from './pages/Account'; import Account from './pages/Account';
import Wishlist from './pages/Wishlist';
// Create QueryClient instance // Create QueryClient instance
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@@ -64,6 +65,9 @@ function App() {
<Route path="/checkout" element={<Checkout />} /> <Route path="/checkout" element={<Checkout />} />
<Route path="/order-received/:orderId" element={<ThankYou />} /> <Route path="/order-received/:orderId" element={<ThankYou />} />
{/* Wishlist - Public route accessible to guests */}
<Route path="/wishlist" element={<Wishlist />} />
{/* My Account */} {/* My Account */}
<Route path="/my-account/*" element={<Account />} /> <Route path="/my-account/*" element={<Account />} />

View File

@@ -146,6 +146,7 @@ export function useWishlist() {
isEnabled, isEnabled,
isLoggedIn, isLoggedIn,
count: items.length, count: items.length,
productIds,
addToWishlist, addToWishlist,
removeFromWishlist, removeFromWishlist,
toggleWishlist, toggleWishlist,

View File

@@ -136,7 +136,7 @@ function ClassicLayout({ children }: BaseLayoutProps) {
{/* Wishlist */} {/* Wishlist */}
{headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && ( {headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && (
<Link to="/my-account/wishlist" className="flex items-center gap-2 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors no-underline"> <Link to="/wishlist" className="flex items-center gap-2 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors no-underline">
<Heart className="h-5 w-5" /> <Heart className="h-5 w-5" />
<span className="hidden lg:block">Wishlist</span> <span className="hidden lg:block">Wishlist</span>
</Link> </Link>
@@ -429,7 +429,7 @@ function ModernLayout({ children }: BaseLayoutProps) {
) )
)} )}
{headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && ( {headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && (
<Link to="/my-account/wishlist" className="flex items-center gap-1 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors no-underline"> <Link to="/wishlist" className="flex items-center gap-1 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors no-underline">
<Heart className="h-4 w-4" /> Wishlist <Heart className="h-4 w-4" /> Wishlist
</Link> </Link>
)} )}
@@ -562,7 +562,7 @@ function BoutiqueLayout({ children }: BaseLayoutProps) {
</a> </a>
))} ))}
{headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && ( {headerSettings.elements.wishlist && isEnabled('wishlist') && (wishlistSettings.show_in_header ?? true) && (
<Link to="/my-account/wishlist" className="flex items-center gap-1 text-sm uppercase tracking-wider text-gray-700 hover:text-gray-900 transition-colors no-underline"> <Link to="/wishlist" className="flex items-center gap-1 text-sm uppercase tracking-wider text-gray-700 hover:text-gray-900 transition-colors no-underline">
<Heart className="h-4 w-4" /> Wishlist <Heart className="h-4 w-4" /> Wishlist
</Link> </Link>
)} )}

View File

@@ -0,0 +1,168 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Trash2, ShoppingCart, Heart } from 'lucide-react';
import { useWishlist } from '@/hooks/useWishlist';
import { useCartStore } from '@/lib/cart/store';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
/**
* Public Wishlist Page - Accessible to both guests and logged-in users
* Guests: Shows items from localStorage
* Logged-in: Shows items from database via API
*/
export default function Wishlist() {
const navigate = useNavigate();
const { items, isLoading, isLoggedIn, removeFromWishlist, productIds } = useWishlist();
const { addItem } = useCartStore();
const handleRemove = async (productId: number) => {
await removeFromWishlist(productId);
};
const handleAddToCart = (productId: number, productName: string) => {
// For guests with localStorage wishlist, we only have IDs
// Navigate to product page for now
navigate(`/product/${productId}`);
};
if (isLoading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="text-center">
<p className="text-gray-600">Loading wishlist...</p>
</div>
</div>
);
}
// Guest mode: only have product IDs from localStorage
const guestWishlistIds = !isLoggedIn ? Array.from(productIds) : [];
const hasGuestItems = guestWishlistIds.length > 0;
const hasLoggedInItems = items.length > 0;
if (!hasGuestItems && !hasLoggedInItems) {
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-6">My Wishlist</h1>
<div className="text-center py-12 bg-gray-50 rounded-lg">
<Heart className="w-16 h-16 mx-auto mb-4 text-gray-400" />
<h2 className="text-xl font-semibold mb-2">Your wishlist is empty</h2>
<p className="text-gray-600 mb-6">
Start adding products you love to your wishlist
</p>
<Button onClick={() => navigate('/shop')}>
Browse Products
</Button>
</div>
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold">My Wishlist</h1>
<p className="text-gray-600">
{isLoggedIn ? `${items.length} items` : `${guestWishlistIds.length} items`}
</p>
</div>
{/* Guest Mode: Show IDs only with limited functionality */}
{!isLoggedIn && hasGuestItems && (
<div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-800">
<strong>Guest Wishlist:</strong> You have {guestWishlistIds.length} items saved locally.
<a href="/wp-login.php" className="underline ml-1">Login</a> to see full details and sync your wishlist.
</p>
</div>
)}
{/* Guest Wishlist Items (localStorage only - show IDs) */}
{!isLoggedIn && hasGuestItems && (
<div className="space-y-4">
{guestWishlistIds.map((productId: number) => (
<div key={`guest-${productId}`} className="bg-white border rounded-lg p-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="w-20 h-20 bg-gray-200 rounded flex items-center justify-center">
<Heart className="w-8 h-8 text-gray-400" />
</div>
<div>
<h3 className="font-semibold">Product #{productId}</h3>
<p className="text-sm text-gray-600">Login to see details</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => navigate(`/product/${productId.toString()}`)}
>
View Product
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemove(productId)}
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
))}
</div>
)}
{/* Logged-in Wishlist Items (full details from API) */}
{isLoggedIn && hasLoggedInItems && (
<div className="space-y-4">
{items.map((item) => (
<div key={item.product_id} className="bg-white border rounded-lg p-4 flex items-center justify-between">
<div className="flex items-center gap-4">
{item.image ? (
<img
src={item.image}
alt={item.name}
className="w-20 h-20 object-cover rounded"
/>
) : (
<div className="w-20 h-20 bg-gray-200 rounded flex items-center justify-center">
<Heart className="w-8 h-8 text-gray-400" />
</div>
)}
<div>
<h3 className="font-semibold">{item.name}</h3>
<p className="text-sm text-gray-600">{item.price}</p>
{item.stock_status === 'outofstock' && (
<p className="text-sm text-red-600">Out of stock</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => navigate(`/product/${item.slug}`)}
>
View
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemove(item.product_id)}
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}