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:
168
customer-spa/src/pages/Wishlist.tsx
Normal file
168
customer-spa/src/pages/Wishlist.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user