Files
WooNooW/admin-spa/src/components/nav/SubmenuBar.tsx
Dwindi Ramadhana cc67288614 fix: Submenu active states + Guest wishlist frontend check
Submenu Active State Fix:
Problem: Dashboard Overview never active, All Orders/Products/Customers always active
Root Cause: Submenu path matching didn't respect 'exact' flag from backend
Solution: Added proper exact flag handling in SubmenuBar component
- If item.exact = true: only match exact path (for Overview at /dashboard)
- If item.exact = false/undefined: match path + sub-paths (for All Orders at /orders)
Result: Submenu items now show correct active state 

Guest Wishlist Frontend Fix:
Problem: Guest wishlist enabled in backend but frontend blocked with login prompt
Root Cause: useWishlist hook had frontend login checks before API calls
Solution: Removed frontend login checks from addToWishlist and removeFromWishlist
- Backend already enforces permission via check_permission() based on enable_guest_wishlist
- Frontend now lets backend handle authorization
- API returns proper error if guest wishlist disabled
Result: Guests can add/remove wishlist items when setting enabled 

Files Modified (2):
- admin-spa/src/components/nav/SubmenuBar.tsx (exact flag handling)
- customer-spa/src/hooks/useWishlist.ts (removed login checks)
- admin-spa/dist/app.js + customer-spa/dist/app.js (rebuilt)

Both submenu and guest wishlist issues resolved!
2025-12-26 22:50:25 +07:00

67 lines
2.7 KiB
TypeScript

import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import type { SubItem } from '@/nav/tree';
type Props = { items?: SubItem[]; fullscreen?: boolean; headerVisible?: boolean };
export default function SubmenuBar({ items = [], fullscreen = false, headerVisible = true }: Props) {
// Always call hooks first
const { pathname } = useLocation();
// Single source of truth: props.items. No fallbacks, no demos, no path-based defaults
if (items.length === 0) return null;
// Hide submenu on mobile for detail/new/edit pages (only show on index)
const isDetailPage = /\/(orders|products|coupons|customers)\/(?:new|\d+(?:\/edit)?)$/.test(pathname);
const hiddenOnMobile = isDetailPage ? 'hidden md:block' : '';
// Calculate top position based on fullscreen state
// Fullscreen: top-0 (no contextual headers, submenu is first element)
// Normal: top-[calc(7rem+32px)] (below WP admin bar + menu bar)
const topClass = fullscreen ? 'top-0' : 'top-[calc(7rem+32px)]';
return (
<div data-submenubar className={`border-b border-border bg-background md:bg-background/95 md:backdrop-blur md:supports-[backdrop-filter]:bg-background/60 ${hiddenOnMobile}`}>
<div className="px-4 py-2">
<div className="flex gap-2 overflow-x-auto no-scrollbar">
{items.map((it) => {
const key = `${it.label}-${it.path || it.href}`;
// Exact match for submenu items, or match with sub-paths (e.g., /settings/notifications matches /settings/notifications/staff)
// Special handling: if item has exact flag, only match exact path
let isActive = false;
if (it.path) {
if (it.exact) {
isActive = pathname === it.path;
} else {
isActive = pathname === it.path || pathname.startsWith(it.path + '/');
}
}
const cls = [
'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',
isActive ? 'bg-accent text-accent-foreground' : 'hover:bg-accent hover:text-accent-foreground',
].join(' ');
if (it.mode === 'spa' && it.path) {
return (
<Link key={key} to={it.path} className={cls} data-discover>
{it.label}
</Link>
);
}
if (it.mode === 'bridge' && it.href) {
return (
<a key={key} href={it.href} className={cls} data-discover>
{it.label}
</a>
);
}
return null;
})}
</div>
</div>
</div>
);
}