242 lines
9.9 KiB
TypeScript
242 lines
9.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
FiX,
|
|
FiTrash2,
|
|
FiShoppingCart,
|
|
FiTag,
|
|
FiCheckCircle,
|
|
FiArrowRight,
|
|
FiLock,
|
|
FiShield
|
|
} from 'react-icons/fi';
|
|
import type { CartItem } from '../../types/course';
|
|
|
|
interface CartDrawerProps {
|
|
isOpen: boolean;
|
|
cartItems: CartItem[];
|
|
onClose: () => void;
|
|
onRemoveItem: (courseId: string) => void;
|
|
onClearCart: () => void;
|
|
}
|
|
|
|
export const CartDrawer: React.FC<CartDrawerProps> = ({
|
|
isOpen,
|
|
cartItems,
|
|
onClose,
|
|
onRemoveItem,
|
|
onClearCart,
|
|
}) => {
|
|
const [couponCode, setCouponCode] = useState('');
|
|
const [appliedDiscount, setAppliedDiscount] = useState<number>(0);
|
|
const [couponMessage, setCouponMessage] = useState<string | null>(null);
|
|
const [isCheckingOut, setIsCheckingOut] = useState(false);
|
|
const [checkoutSuccess, setCheckoutSuccess] = useState(false);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const rawSubtotal = cartItems.reduce((acc, item) => acc + item.course.price, 0);
|
|
const discountAmount = (rawSubtotal * appliedDiscount) / 100;
|
|
const finalTotal = Math.max(0, rawSubtotal - discountAmount);
|
|
|
|
const handleApplyCoupon = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const code = couponCode.trim().toUpperCase();
|
|
if (code === 'DESCUENTO20' || code === 'PROMO20') {
|
|
setAppliedDiscount(20);
|
|
setCouponMessage('¡Cupón del 20% aplicado con éxito!');
|
|
} else if (code === 'PROMO50' || code === 'RYNEXT50') {
|
|
setAppliedDiscount(50);
|
|
setCouponMessage('¡Cupón especial del 50% de descuento aplicado!');
|
|
} else if (code.length > 0) {
|
|
setCouponMessage('Cupón no válido. Prueba con "DESCUENTO20" o "RYNEXT50"');
|
|
}
|
|
};
|
|
|
|
const handleCheckout = () => {
|
|
setIsCheckingOut(true);
|
|
setTimeout(() => {
|
|
setIsCheckingOut(false);
|
|
setCheckoutSuccess(true);
|
|
setTimeout(() => {
|
|
setCheckoutSuccess(false);
|
|
onClearCart();
|
|
onClose();
|
|
}, 3000);
|
|
}, 1500);
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 overflow-hidden bg-slate-950/70 backdrop-blur-sm animate-in fade-in duration-200">
|
|
<div className="absolute inset-y-0 right-0 max-w-full flex pl-10">
|
|
|
|
{/* Slide panel */}
|
|
<div className="w-screen max-w-md bg-slate-900 border-l border-slate-800 shadow-2xl flex flex-col justify-between">
|
|
|
|
{/* Top Header */}
|
|
<div className="p-6 border-b border-slate-800 flex items-center justify-between bg-slate-950/50">
|
|
<div className="flex items-center gap-2.5">
|
|
<div className="p-2 rounded-lg bg-cyan-500/10 text-cyan-400">
|
|
<FiShoppingCart className="text-lg" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-base font-bold text-white">Carrito de Compras</h3>
|
|
<p className="text-xs text-slate-400">
|
|
{cartItems.length} {cartItems.length === 1 ? 'curso seleccionado' : 'cursos seleccionados'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 rounded-xl text-slate-400 hover:text-white hover:bg-slate-800 transition"
|
|
aria-label="Cerrar carrito"
|
|
>
|
|
<FiX className="text-xl" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Cart Items List */}
|
|
<div className="flex-1 overflow-y-auto p-6 space-y-4">
|
|
{checkoutSuccess ? (
|
|
<div className="text-center py-12 space-y-4 animate-in zoom-in-95 duration-300">
|
|
<div className="w-16 h-16 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center mx-auto text-3xl">
|
|
<FiCheckCircle />
|
|
</div>
|
|
<h4 className="text-xl font-bold text-white">¡Compra Realizada con Éxito!</h4>
|
|
<p className="text-xs text-slate-300 max-w-xs mx-auto">
|
|
Hemos enviado los accesos a tu correo. Tus cursos ya están disponibles en tu biblioteca de aprendizaje.
|
|
</p>
|
|
<div className="p-3 bg-slate-950 rounded-xl border border-slate-800 text-xs text-cyan-400 font-mono">
|
|
Orden #RY-{Math.floor(100000 + Math.random() * 900000)}
|
|
</div>
|
|
</div>
|
|
) : cartItems.length > 0 ? (
|
|
cartItems.map((item) => (
|
|
<div
|
|
key={item.course.id}
|
|
className="flex gap-3.5 p-3 rounded-xl bg-slate-950/60 border border-slate-800/80 hover:border-slate-700 transition"
|
|
>
|
|
<img
|
|
src={item.course.thumbnail}
|
|
alt={item.course.title}
|
|
className="w-20 h-14 object-cover rounded-lg bg-slate-800 shrink-0"
|
|
/>
|
|
|
|
<div className="flex-1 min-w-0 flex flex-col justify-between">
|
|
<div>
|
|
<h4 className="text-xs font-bold text-white line-clamp-1">
|
|
{item.course.title}
|
|
</h4>
|
|
<p className="text-[11px] text-slate-400 truncate">
|
|
Por {item.course.instructor.name}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between pt-1">
|
|
<span className="text-sm font-bold text-white font-mono">
|
|
${item.course.price.toFixed(2)}
|
|
</span>
|
|
|
|
<button
|
|
onClick={() => onRemoveItem(item.course.id)}
|
|
className="text-slate-500 hover:text-rose-400 text-xs flex items-center gap-1 transition"
|
|
title="Eliminar curso"
|
|
>
|
|
<FiTrash2 />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="text-center py-16 space-y-4">
|
|
<div className="w-16 h-16 rounded-full bg-slate-800 text-slate-500 flex items-center justify-center mx-auto text-2xl">
|
|
<FiShoppingCart />
|
|
</div>
|
|
<h4 className="text-sm font-bold text-white">Tu carrito está vacío</h4>
|
|
<p className="text-xs text-slate-400 max-w-xs mx-auto">
|
|
Explora nuestro catálogo de cursos en desarrollo, IA y diseño para comenzar.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Bottom Summary & Checkout Form */}
|
|
{cartItems.length > 0 && !checkoutSuccess && (
|
|
<div className="p-6 border-t border-slate-800 bg-slate-950/90 space-y-4">
|
|
|
|
{/* Coupon Form */}
|
|
<form onSubmit={handleApplyCoupon} className="space-y-1.5">
|
|
<div className="flex gap-2">
|
|
<div className="relative flex-1">
|
|
<FiTag className="absolute left-3 top-3 text-slate-500 text-xs" />
|
|
<input
|
|
type="text"
|
|
value={couponCode}
|
|
onChange={(e) => setCouponCode(e.target.value)}
|
|
placeholder="Cupón (ej. DESCUENTO20)"
|
|
className="w-full pl-8 pr-3 py-2 bg-slate-900 border border-slate-700 rounded-lg text-xs text-white placeholder-slate-500 focus:outline-none focus:border-cyan-500 uppercase"
|
|
/>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
className="px-3 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold text-xs rounded-lg transition"
|
|
>
|
|
Aplicar
|
|
</button>
|
|
</div>
|
|
{couponMessage && (
|
|
<p className={`text-[11px] ${appliedDiscount > 0 ? 'text-emerald-400' : 'text-amber-400'}`}>
|
|
{couponMessage}
|
|
</p>
|
|
)}
|
|
</form>
|
|
|
|
{/* Price Breakdown */}
|
|
<div className="space-y-1.5 text-xs text-slate-400 pt-2 border-t border-slate-800">
|
|
<div className="flex justify-between">
|
|
<span>Subtotal</span>
|
|
<span className="font-mono text-slate-200">${rawSubtotal.toFixed(2)}</span>
|
|
</div>
|
|
{appliedDiscount > 0 && (
|
|
<div className="flex justify-between text-emerald-400">
|
|
<span>Descuento ({appliedDiscount}%)</span>
|
|
<span className="font-mono">-${discountAmount.toFixed(2)}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between text-sm font-bold text-white pt-2 border-t border-slate-800">
|
|
<span>Total a pagar</span>
|
|
<span className="text-xl text-cyan-400 font-mono">${finalTotal.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Checkout Button */}
|
|
<button
|
|
onClick={handleCheckout}
|
|
disabled={isCheckingOut}
|
|
className="w-full py-3.5 bg-gradient-to-r from-blue-600 via-cyan-600 to-teal-500 hover:from-blue-500 hover:to-cyan-400 text-white font-bold rounded-xl text-sm shadow-xl shadow-cyan-500/25 transition-all flex items-center justify-center gap-2 cursor-pointer disabled:opacity-50"
|
|
>
|
|
{isCheckingOut ? (
|
|
<span>Procesando pago seguro...</span>
|
|
) : (
|
|
<>
|
|
<FiLock className="text-sm" />
|
|
<span>Completar Pago Seguro</span>
|
|
<FiArrowRight className="text-sm" />
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
<div className="flex items-center justify-center gap-2 text-[11px] text-slate-500">
|
|
<FiShield className="text-emerald-400" />
|
|
<span>Garantía total de devolución de 30 días</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|