186 lines
7.2 KiB
TypeScript
186 lines
7.2 KiB
TypeScript
'use client';
|
||
|
||
import Link from 'next/link';
|
||
import { Heart, ShoppingBag } from 'lucide-react';
|
||
import { useWishlist } from '@/presentation/providers/wishlist-provider';
|
||
import { useCart } from '@/presentation/providers/cart-provider';
|
||
import { useLanguage } from '@/presentation/providers/language-provider';
|
||
import { getTranslation } from '@/presentation/i18n/translations';
|
||
import { getMenuPosterSrc, applyNextImageFallback, getMenuPosterCandidates } from '@/lib/assets';
|
||
import { useMenu } from '@/presentation/providers/menu-provider';
|
||
import { getMenuItemName } from '@/application/i18n/menu-localization';
|
||
import { buildCartLineFromMenuItem } from '@/application/cart/cart-line-builder';
|
||
|
||
export default function WishlistDrawer() {
|
||
const {
|
||
items,
|
||
isOpen,
|
||
closeWishlist,
|
||
removeFromWishlist,
|
||
clearWishlist,
|
||
} = useWishlist();
|
||
|
||
const { addToCart } = useCart();
|
||
const { getItemById } = useMenu();
|
||
const { language, isRtl } = useLanguage();
|
||
const t = getTranslation(language);
|
||
|
||
if (!isOpen) return null;
|
||
|
||
const handleAddToCart = (item: (typeof items)[number]) => {
|
||
const menuItem = getItemById(item.id);
|
||
addToCart(menuItem ? buildCartLineFromMenuItem(menuItem) : {
|
||
id: item.id,
|
||
name: item.name,
|
||
price: item.price,
|
||
image: item.image,
|
||
pricingMode: 'standard',
|
||
});
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div
|
||
className="fixed inset-0 bg-black/40 z-[985]"
|
||
onClick={closeWishlist}
|
||
/>
|
||
|
||
<div
|
||
className={`fixed top-0 h-full w-full max-w-md bg-[#F8F5F0] z-[985] shadow-2xl flex flex-col ${
|
||
isRtl ? 'left-0 border-r border-[#EDE6D9]' : 'right-0 border-l border-[#EDE6D9]'
|
||
}`}
|
||
>
|
||
<div className="flex items-center justify-between p-6 border-b border-[#EDE6D9]">
|
||
<div className="flex items-center gap-3">
|
||
<Heart className="h-5 w-5 text-[#c99a2e] fill-[#c99a2e]/20" />
|
||
<h2 className="text-2xl tracking-[-0.5px] text-[#101724]">{t.wishlistDrawer.title}</h2>
|
||
{items.length > 0 && (
|
||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-[#EDE6D9] text-[#6B665F]">
|
||
{items.length} {items.length === 1 ? t.wishlistDrawer.item : t.wishlistDrawer.items}
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
{items.length > 0 && (
|
||
<button
|
||
onClick={clearWishlist}
|
||
className="text-xs text-[#B38B4D] hover:underline"
|
||
>
|
||
{t.wishlistDrawer.clear}
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={closeWishlist}
|
||
className="text-[#6B665F] hover:text-[#2C2A26] text-2xl leading-none pl-1"
|
||
aria-label="Close wishlist"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto p-6">
|
||
{items.length === 0 ? (
|
||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||
<Heart className="h-16 w-16 text-[#c99a2e]/30 mb-4" />
|
||
<p className="text-lg text-[#6B665F]">{t.wishlistDrawer.empty}</p>
|
||
<Link
|
||
href="/menu"
|
||
onClick={closeWishlist}
|
||
className="mt-6 text-[#B38B4D] hover:underline"
|
||
>
|
||
{t.wishlistDrawer.browseMenu}
|
||
</Link>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-5">
|
||
{items.map((item) => {
|
||
const menuItem = getItemById(item.id);
|
||
const displayName = getMenuItemName(language, {
|
||
id: item.id,
|
||
name: menuItem?.name ?? item.name,
|
||
});
|
||
|
||
return (
|
||
<div
|
||
key={item.id}
|
||
className="flex gap-4 rounded-2xl border border-[#EDE6D9] bg-white p-4"
|
||
>
|
||
<div className="h-20 w-20 shrink-0 overflow-hidden rounded-xl bg-[#F2EDE4]">
|
||
<img
|
||
src={getMenuPosterSrc(item)}
|
||
alt={displayName}
|
||
className="h-full w-full object-cover"
|
||
loading="lazy"
|
||
onError={(e) => {
|
||
applyNextImageFallback(
|
||
e.target as HTMLImageElement,
|
||
getMenuPosterCandidates(item),
|
||
(e.target as HTMLImageElement).src
|
||
);
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex justify-between gap-2">
|
||
<h4 className="font-medium tracking-[-0.3px] text-[#101724] truncate">
|
||
{displayName}
|
||
</h4>
|
||
<span className="shrink-0 text-right font-medium text-[#B38B4D] tabular-nums text-sm leading-tight">
|
||
{(() => {
|
||
const menuItem = getItemById(item.id);
|
||
if (menuItem?.pricing === 'weight') {
|
||
return (
|
||
<>
|
||
<span className="block">{menuItem.pricePerHalfKg} kr / ½ kg</span>
|
||
<span className="block text-xs text-[#8A8478]">{menuItem.pricePerKg} {t.menu.perKg}</span>
|
||
</>
|
||
);
|
||
}
|
||
return <span>{item.price} kr</span>;
|
||
})()}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2 mt-3">
|
||
<button
|
||
onClick={() => handleAddToCart(item)}
|
||
className="inline-flex items-center gap-1.5 rounded-full bg-gradient-to-r from-[#c99a2e] to-[#d4a73d] px-4 py-2 text-xs font-bold text-[#241806] transition-all hover:scale-[1.02] active:scale-[0.98]"
|
||
>
|
||
<ShoppingBag className="h-3.5 w-3.5" />
|
||
{t.wishlistDrawer.addToCart}
|
||
</button>
|
||
<button
|
||
onClick={() => removeFromWishlist(item.id)}
|
||
className="text-xs text-[#B38B4D] hover:underline ml-auto"
|
||
>
|
||
{t.wishlistDrawer.remove}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{items.length > 0 && (
|
||
<div className="p-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] border-t border-[#EDE6D9] bg-white">
|
||
<p className="text-xs text-center text-[#6B665F] mb-4">
|
||
{t.wishlistDrawer.hint}
|
||
</p>
|
||
<Link
|
||
href="/menu"
|
||
onClick={closeWishlist}
|
||
className="block w-full text-center text-sm text-[#B38B4D] hover:underline"
|
||
>
|
||
{t.wishlistDrawer.continueBrowsing}
|
||
</Link>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
);
|
||
} |