Initial commit

This commit is contained in:
Rami Bitar
2026-04-19 11:17:41 -04:00
commit b5a79b6475
77 changed files with 10416 additions and 0 deletions

206
components/CartDrawer.tsx Normal file
View File

@@ -0,0 +1,206 @@
"use client";
import React, { useState } from 'react';
import { RiCloseLine, RiImageLine, RiSubtractLine, RiAddLine, RiLoader4Line } from '@remixicon/react';
import { useShopifyCart, redirectToCheckout } from '@/hooks/use-shopify-cart';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetBody,
SheetFooter,
AnimatePresence,
} from './ui/sheet';
import { Button } from './ui/button';
const CartDrawer: React.FC = () => {
const {
isOpen,
items,
itemCount,
totalAmount,
checkoutUrl,
removeItem,
updateItemQuantity,
closeCart
} = useShopifyCart();
const [isCheckingOut, setIsCheckingOut] = useState(false);
const handleCheckout = async () => {
if (!checkoutUrl) return;
setIsCheckingOut(true);
try {
redirectToCheckout(checkoutUrl);
} catch (error) {
console.error('Error during checkout:', error);
alert('Failed to proceed to checkout. Please try again.');
setIsCheckingOut(false);
}
};
return (
<Sheet open={isOpen} onOpenChange={(open) => !open && closeCart()}>
<AnimatePresence>
{isOpen && (
<SheetContent className="w-full max-w-md" showCloseButton={false}>
{/* Header */}
<SheetHeader className="h-16 justify-center items-start">
<div className="flex items-center justify-between w-full">
<SheetTitle
className="text-2xl font-bold font-heading"
>
Shopping Cart ({itemCount})
</SheetTitle>
<button
onClick={closeCart}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<RiCloseLine size={18} />
</button>
</div>
</SheetHeader>
{/* Cart Items */}
<SheetBody>
{items.length === 0 ? (
<div className="text-center py-12">
<h3 className="text-lg font-semibold text-gray-600 mb-2">Your cart is empty</h3>
<p className="text-gray-500 mb-6">Add some products to get started!</p>
<Button onClick={closeCart}>
Continue Shopping
</Button>
</div>
) : (
<div className="space-y-6">
{items.map((item) => (
<div key={item.id} className="flex items-start space-x-4 pb-6 border-b border-gray-200 last:border-b-0">
{/* Product Image */}
<div className="w-20 h-20 bg-gray-100 rounded-lg overflow-hidden flex-shrink-0">
{item.merchandise.image?.url ? (
<img
src={item.merchandise.image.url}
alt={item.merchandise.image.altText || item.merchandise.product.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400">
<RiImageLine size={18} />
</div>
)}
</div>
{/* Product Details */}
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-gray-900 mb-1 line-clamp-2">
{item.merchandise.product.title}
</h4>
{/* Variant Info */}
{item.merchandise.selectedOptions && item.merchandise.selectedOptions.length > 0 && (
<div className="text-sm text-gray-500 mb-2">
{item.merchandise.selectedOptions.map((option, index) => (
<span key={option.name}>
{option.value}
{index < item.merchandise.selectedOptions.length - 1 ? ' / ' : ''}
</span>
))}
</div>
)}
{/* Quantity Controls */}
<div className="flex items-center mt-3">
<div className="flex items-center border border-gray-300 rounded-lg">
<button
onClick={() => updateItemQuantity(item.id, item.quantity - 1)}
className="p-1 hover:bg-gray-100 transition-colors text-gray-500"
disabled={item.quantity <= 1}
>
<RiSubtractLine size={12} />
</button>
<span className="px-2 py-1 font-semibold min-w-[30px] text-center text-sm">
{item.quantity}
</span>
<button
onClick={() => updateItemQuantity(item.id, item.quantity + 1)}
className="p-1 hover:bg-gray-100 transition-colors text-gray-500"
>
<RiAddLine size={12} />
</button>
</div>
</div>
</div>
{/* Price */}
<div className="flex-shrink-0">
<span className="font-semibold text-gray-900">
${parseFloat(item.merchandise.price.amount).toFixed(2)}
</span>
</div>
{/* Remove Button */}
<div className="flex-shrink-0">
<button
onClick={() => removeItem(item.id)}
className="p-1 hover:bg-gray-100 rounded transition-colors text-gray-400 hover:text-red-500"
>
<RiCloseLine size={14} />
</button>
</div>
</div>
))}
</div>
)}
</SheetBody>
{/* Footer - Checkout Section */}
{items.length > 0 && (
<SheetFooter className="!flex-col items-stretch gap-3">
{/* Subtotal */}
<div className="flex items-center justify-between mb-2">
<span className="text-lg font-semibold">Subtotal</span>
<span className="text-lg font-semibold">
${totalAmount.toFixed(2)}
</span>
</div>
<div className="text-sm text-gray-500 mb-2">
Shipping and taxes calculated at checkout
</div>
{/* Action Buttons */}
<Button
onClick={handleCheckout}
disabled={isCheckingOut || !checkoutUrl}
size="lg"
className="w-full"
>
{isCheckingOut ? (
<span className="flex items-center justify-center space-x-2">
<RiLoader4Line size={14} className="animate-spin" />
<span>Processing...</span>
</span>
) : (
'Checkout'
)}
</Button>
<Button
onClick={closeCart}
variant="outline"
size="lg"
className="w-full"
>
Continue Shopping
</Button>
</SheetFooter>
)}
</SheetContent>
)}
</AnimatePresence>
</Sheet>
);
};
export default CartDrawer;