575 lines
22 KiB
TypeScript
575 lines
22 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { X, CheckCircle, ShoppingCart, Trash2, Shield, ArrowRight } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'motion/react';
|
|
import { Button } from './ui/button';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { navigateTo } from './Router';
|
|
import { useCart } from './CartContext';
|
|
|
|
export interface CartItem {
|
|
id: string;
|
|
title: string;
|
|
thumbnail: string;
|
|
price: string;
|
|
originalPrice?: string;
|
|
category: string;
|
|
level: string;
|
|
type?: string;
|
|
}
|
|
|
|
interface CartPopupProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
// cartItems: CartItem[]; // Legacy prop - no longer used but kept for backward compatibility
|
|
// onRemoveItem: (itemId: string) => void; // Legacy prop - no longer used but kept for backward compatibility
|
|
recentlyAddedItem?: CartItem | null;
|
|
}
|
|
|
|
export function CartPopup({
|
|
isOpen,
|
|
onClose,
|
|
// cartItems: legacyCartItems, // Renamed to avoid confusion
|
|
// onRemoveItem: legacyOnRemoveItem, // Renamed to avoid confusion
|
|
recentlyAddedItem
|
|
}: CartPopupProps) {
|
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
|
|
// Use global cart context instead of props
|
|
const { cartItems, removeFromCart, getCartTotal } = useCart();
|
|
|
|
useEffect(() => {
|
|
if (recentlyAddedItem && isOpen) {
|
|
setShowSuccess(true);
|
|
const timer = setTimeout(() => setShowSuccess(false), 3000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [recentlyAddedItem, isOpen]);
|
|
|
|
// Prevent background scrolling when popup is open
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
// Save current scroll position
|
|
const scrollY = window.scrollY;
|
|
|
|
// Prevent scrolling
|
|
document.body.style.overflow = 'hidden';
|
|
document.body.style.position = 'fixed';
|
|
document.body.style.top = `-${scrollY}px`;
|
|
document.body.style.width = '100%';
|
|
|
|
return () => {
|
|
// Restore scrolling
|
|
document.body.style.overflow = '';
|
|
document.body.style.position = '';
|
|
document.body.style.top = '';
|
|
document.body.style.width = '';
|
|
|
|
// Restore scroll position
|
|
window.scrollTo(0, scrollY);
|
|
};
|
|
}
|
|
}, [isOpen]);
|
|
|
|
// Fixed price parsing function - handles both ₹ and $ formats
|
|
const parsePrice = (priceStr: string) => {
|
|
// Remove currency symbols and commas, then parse
|
|
const cleanPrice = priceStr.replace(/[₹$,]/g, '');
|
|
const numValue = parseFloat(cleanPrice);
|
|
return isNaN(numValue) ? 0 : numValue;
|
|
};
|
|
|
|
// Convert to rupees - improved handling
|
|
const convertToRupees = (priceStr: string) => {
|
|
const numValue = parsePrice(priceStr);
|
|
|
|
// If already in rupees, return as is
|
|
if (priceStr.includes('₹')) {
|
|
return `₹${numValue.toLocaleString('en-IN')}`;
|
|
}
|
|
|
|
// Convert from USD to INR
|
|
return `₹${(numValue * 83).toLocaleString('en-IN')}`;
|
|
};
|
|
|
|
// Use global cart total instead of calculating locally
|
|
const subtotal = getCartTotal();
|
|
const estimatedTotal = subtotal; // No taxes for now
|
|
|
|
const handleProceedToCheckout = () => {
|
|
// Navigate to checkout page (placeholder)
|
|
navigateTo('/checkout');
|
|
onClose();
|
|
};
|
|
|
|
const handleContinueShopping = () => {
|
|
onClose();
|
|
};
|
|
|
|
// Handle backdrop click - close popup only if clicking on backdrop
|
|
const handleBackdropClick = (e: React.MouseEvent) => {
|
|
if (e.target === e.currentTarget) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop with higher z-index than navbar */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
|
|
style={{ zIndex: 10001 }} // Higher than navbar (navbar typically uses z-50 which is 50)
|
|
onClick={handleBackdropClick}
|
|
/>
|
|
|
|
{/* Cart Popup with highest z-index */}
|
|
<motion.div
|
|
initial={{ x: '100%' }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: '100%' }}
|
|
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
|
className="fixed top-0 right-0 h-full w-full max-w-lg bg-white shadow-2xl overflow-hidden flex flex-col"
|
|
style={{
|
|
zIndex: 10002, // Highest z-index to be above everything including navbar
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-gray-200 bg-white">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center">
|
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
|
</div>
|
|
<div>
|
|
<h2
|
|
className="font-semibold"
|
|
style={{
|
|
fontSize: 'var(--font-h4)',
|
|
fontWeight: 'var(--font-weight-h4)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Added to Cart
|
|
</h2>
|
|
<p
|
|
className="text-muted"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''} in your cart
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-gray-100 rounded-full"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Success Message */}
|
|
<AnimatePresence>
|
|
{showSuccess && recentlyAddedItem && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="mx-6 mt-4 p-4 bg-green-50 border border-green-200 rounded-lg"
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<CheckCircle className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
|
|
<div className="flex-1">
|
|
<p
|
|
className="font-medium text-green-800"
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
"{recentlyAddedItem.title}" added successfully!
|
|
</p>
|
|
<p
|
|
className="text-green-600 mt-1"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
You can continue browsing or proceed to checkout.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Cart Content - Scrollable */}
|
|
<div className="flex-1 overflow-y-auto">
|
|
{/* Cart Items */}
|
|
<div className="p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3
|
|
style={{
|
|
fontSize: 'var(--font-h4)',
|
|
fontWeight: 'var(--font-weight-h4)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Your Cart ({cartItems.length})
|
|
</h3>
|
|
<p
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '500'
|
|
}}
|
|
>
|
|
Total: ₹{subtotal.toLocaleString('en-IN')}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Cart Items List */}
|
|
<div className="space-y-4">
|
|
{cartItems.map((item, index) => (
|
|
<motion.div
|
|
key={item.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: index * 0.1 }}
|
|
className="flex items-start gap-4 p-4 border border-gray-200 rounded-lg hover:border-gray-300 transition-colors"
|
|
>
|
|
{/* Course Thumbnail */}
|
|
<div className="w-20 h-14 rounded-lg overflow-hidden flex-shrink-0">
|
|
<ImageWithFallback
|
|
src={item.thumbnail}
|
|
alt={item.title}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Course Details */}
|
|
<div className="flex-1 min-w-0">
|
|
<h4
|
|
className="font-medium line-clamp-2 mb-1"
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
fontWeight: '500',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{item.title}
|
|
</h4>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span
|
|
className="text-xs px-2 py-1 bg-gray-100 rounded-md"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Programme
|
|
</span>
|
|
<span
|
|
className="text-xs px-2 py-1 rounded-md"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-primary)',
|
|
backgroundColor: 'rgba(4, 4, 91, 0.1)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{item.level}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className="font-semibold"
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-primary)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{convertToRupees(item.price)}
|
|
</span>
|
|
{item.originalPrice && (
|
|
<span
|
|
className="line-through"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
{convertToRupees(item.originalPrice)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => removeFromCart(item.id)}
|
|
className="p-2 text-red-500 hover:text-red-700 hover:bg-red-50"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Cart Summary */}
|
|
{cartItems.length > 0 && (
|
|
<div className="mt-6 p-4 bg-gray-50 rounded-lg border">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<ShoppingCart className="w-5 h-5 text-gray-600" />
|
|
<h4
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
fontWeight: '600',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Cart Summary
|
|
</h4>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Total Items:
|
|
</span>
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '500'
|
|
}}
|
|
>
|
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Subtotal:
|
|
</span>
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
₹{subtotal.toLocaleString('en-IN')}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between font-semibold pt-2 border-t border-gray-200">
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
Estimated Total:
|
|
</span>
|
|
<span
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-primary)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '700'
|
|
}}
|
|
>
|
|
₹{estimatedTotal.toLocaleString('en-IN')}
|
|
</span>
|
|
</div>
|
|
<p
|
|
className="text-center mt-2"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Final total calculated at checkout
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Total Value Highlight Box */}
|
|
{cartItems.length > 0 && (
|
|
<div
|
|
className="mt-6 p-6 rounded-lg text-center"
|
|
style={{ backgroundColor: 'rgba(4, 4, 91, 0.05)' }}
|
|
>
|
|
<h4
|
|
className="mb-2"
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
fontWeight: '600',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Total Value
|
|
</h4>
|
|
<div
|
|
className="mb-2"
|
|
style={{
|
|
fontSize: '2rem',
|
|
fontWeight: '700',
|
|
color: 'var(--color-primary)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
₹{estimatedTotal.toLocaleString('en-IN')}
|
|
</div>
|
|
<p
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-primary)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '500'
|
|
}}
|
|
>
|
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''} selected
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty Cart Message */}
|
|
{cartItems.length === 0 && (
|
|
<div className="text-center py-8">
|
|
<ShoppingCart className="w-16 h-16 mx-auto text-gray-300 mb-4" />
|
|
<h3
|
|
className="mb-2"
|
|
style={{
|
|
fontSize: 'var(--font-h4)',
|
|
fontWeight: 'var(--font-weight-h4)',
|
|
color: 'var(--color-black)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Your cart is empty
|
|
</h3>
|
|
<p
|
|
className="mb-6"
|
|
style={{
|
|
fontSize: 'var(--font-body)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Browse our courses and add programmes to get started.
|
|
</p>
|
|
<Button
|
|
onClick={handleContinueShopping}
|
|
className="bg-primary text-white hover:bg-primary/90"
|
|
style={{
|
|
backgroundColor: 'var(--color-primary)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Browse Courses
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer Actions */}
|
|
{cartItems.length > 0 && (
|
|
<div className="border-t border-gray-200 p-6 bg-white">
|
|
{/* CTA Buttons */}
|
|
<div className="space-y-3 mb-4">
|
|
{/* Proceed to Checkout - Primary */}
|
|
<Button
|
|
onClick={handleProceedToCheckout}
|
|
className="w-full flex items-center justify-center gap-2 h-12 rounded-lg font-medium"
|
|
style={{
|
|
backgroundColor: 'var(--color-primary)',
|
|
color: 'white',
|
|
fontSize: 'var(--font-body)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
<ShoppingCart className="w-5 h-5" />
|
|
Proceed to Checkout
|
|
</Button>
|
|
|
|
{/* Continue Shopping - Secondary */}
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleContinueShopping}
|
|
className="w-full flex items-center justify-center gap-2 h-12 rounded-lg font-medium"
|
|
style={{
|
|
borderColor: 'var(--color-primary)',
|
|
color: 'var(--color-primary)',
|
|
fontSize: 'var(--font-body)',
|
|
fontFamily: 'var(--font-family-base)',
|
|
fontWeight: '500',
|
|
borderWidth: '2px'
|
|
}}
|
|
>
|
|
Continue Shopping
|
|
<ArrowRight className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Security Footer */}
|
|
<div className="flex items-center justify-center gap-2 pt-3 border-t border-gray-100">
|
|
<Shield className="w-4 h-4 text-green-600" />
|
|
<p
|
|
className="text-center"
|
|
style={{
|
|
fontSize: 'var(--font-small)',
|
|
color: 'var(--color-gray-muted)',
|
|
fontFamily: 'var(--font-family-base)'
|
|
}}
|
|
>
|
|
Secure checkout • 30-day money-back guarantee
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
} |