almeira-web/sneaky-app/src/app/checkout/page.tsx

481 lines
22 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { ArrowLeft, CheckCircle2, ShieldCheck, Truck, Zap, QrCode, Phone, MapPin, User, MessageCircle, ExternalLink, CreditCard } from 'lucide-react';
import { City, PaymentMethod, Order, StoreSettings } from '@/types';
import { INITIAL_CITIES, INITIAL_PAYMENT_METHODS, INITIAL_PRODUCTS } from '@/lib/data-service';
import { formatCOP } from '@/lib/pricing-engine';
import { useBrand } from '@/context/BrandContext';
import confetti from 'canvas-confetti';
export default function CheckoutPage() {
const { currentBrand } = useBrand();
const [customerName, setCustomerName] = useState('');
const [customerPhone, setCustomerPhone] = useState('');
const [address, setAddress] = useState('');
const [neighborhood, setNeighborhood] = useState('');
const [notes, setNotes] = useState('');
const [cities, setCities] = useState<City[]>(INITIAL_CITIES);
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>(INITIAL_PAYMENT_METHODS);
const [selectedCityId, setSelectedCityId] = useState<string>('city-cali');
const [selectedPaymentId, setSelectedPaymentId] = useState<string>('pay-deposit-10k');
const [storeSettings, setStoreSettings] = useState<StoreSettings | null>(null);
const [checkoutItems] = useState<Array<{
productId: string;
productName: string;
brand: string;
size: string;
quantity: number;
price: number;
image: string;
}>>(
INITIAL_PRODUCTS.length > 0
? [
{
productId: INITIAL_PRODUCTS[0].id,
productName: INITIAL_PRODUCTS[0].name,
brand: INITIAL_PRODUCTS[0].brand,
size: '39',
quantity: 1,
price: INITIAL_PRODUCTS[0].retailPrice,
image: INITIAL_PRODUCTS[0].images[0],
},
]
: []
);
const [isSubmitting, setIsSubmitting] = useState(false);
const [orderCompleted, setOrderCompleted] = useState<Order | null>(null);
useEffect(() => {
fetch('/api/cities')
.then((r) => r.json())
.then((d) => d.success && setCities(d.cities))
.catch(() => {});
fetch('/api/payment-methods')
.then((r) => r.json())
.then((d) => d.success && setPaymentMethods(d.paymentMethods))
.catch(() => {});
fetch('/api/settings')
.then((r) => r.json())
.then((d) => d.success && setStoreSettings(d.settings))
.catch(() => {});
}, []);
const activeCity = cities.find((c) => c.id === selectedCityId) || cities[0];
const activePayment = paymentMethods.find((p) => p.id === selectedPaymentId) || paymentMethods[0];
const subtotal = checkoutItems.reduce((acc, it) => acc + it.price * it.quantity, 0);
const shippingCost = activeCity.shippingCost;
const totalAmount = subtotal + shippingCost;
const depositAmount = activePayment.requiresDeposit ? activePayment.depositAmount : 0;
const balanceDue = totalAmount - depositAmount;
const officialWhatsapp = storeSettings?.whatsappNumber || '573167455877';
const boldLink = storeSettings?.boldPaymentLink || 'https://bold.co/p/carlos-lasso';
const handleSubmitOrder = async (e: React.FormEvent) => {
e.preventDefault();
if (!customerName || !customerPhone || !address) {
alert('Por favor completa tu nombre, celular y dirección para el despacho.');
return;
}
setIsSubmitting(true);
try {
const orderPayload = {
customerName,
customerPhone,
address: `${address} - Barrio: ${neighborhood || 'Centro'}`,
cityId: activeCity.id,
cityName: activeCity.name,
items: checkoutItems,
subtotal,
shippingCost,
totalAmount,
depositAmount,
balanceDue,
paymentMethodId: activePayment.id,
paymentMethodName: activePayment.name,
notes,
};
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderPayload),
});
const data = await res.json();
if (data.success) {
setOrderCompleted(data.order);
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
});
}
} catch (err) {
console.error('Error creando orden:', err);
} finally {
setIsSubmitting(false);
}
};
const handleOpenWhatsAppOrder = (order: Order) => {
const text = `🎉 *CONFIRMACIÓN DE PEDIDO EN ${currentBrand.name} #${order.id}* 🎉
👤 *Cliente:* ${order.customerName}
📱 *Teléfono:* ${order.customerPhone}
📍 *Dirección:* ${order.address}
🏙️ *Ciudad:* ${order.cityName}
📦 *Artículos:*
${order.items.map((i) => `• ${i.productName} (Talla ${i.size}) x${i.quantity} = ${formatCOP(i.price * i.quantity)}`).join('\n')}
💰 *Total:* ${formatCOP(order.totalAmount)}
${order.depositAmount > 0 ? `⚡ *Separación:* ${formatCOP(order.depositAmount)}\n🤝 *Saldo Contraentrega:* ${formatCOP(order.balanceDue)}` : '🤝 *Pago 100% Contraentrega.*'}
💳 *Método de Pago:* ${order.paymentMethodName}
¡Hola ${currentBrand.shortName}! Acabo de registrar mi pedido en la web. ¿Me confirman despacho por favor?`;
window.open(`https://wa.me/${officialWhatsapp}?text=${encodeURIComponent(text)}`, '_blank');
};
if (orderCompleted) {
return (
<div className={`min-h-screen ${currentBrand.colors.bodyBg} flex items-center justify-center p-4`}>
<div className={`bg-zinc-900 border ${currentBrand.colors.border} rounded-3xl max-w-lg w-full p-6 sm:p-8 shadow-2xl text-center space-y-5 animate-in zoom-in-95 duration-300`}>
<div className={`w-16 h-16 rounded-full bg-gradient-to-r ${currentBrand.colors.primaryGradient} text-white flex items-center justify-center mx-auto shadow-md`}>
<CheckCircle2 className="w-10 h-10" />
</div>
<div>
<span className={`text-xs font-bold bg-white/10 text-white px-3 py-1 rounded-full uppercase tracking-wider border border-white/10`}>
¡Pedido Registrado con Éxito!
</span>
<h2 className="text-2xl font-black text-white mt-2">Orden #{orderCompleted.id}</h2>
<p className="text-xs text-zinc-400 mt-1">
Hemos recibido tu solicitud para entrega en <strong>{orderCompleted.cityName}</strong> con <strong>{currentBrand.name}</strong>.
</p>
</div>
{/* Bold.co or Nequi Separation Box */}
{orderCompleted.depositAmount > 0 ? (
<div className="bg-zinc-950 border border-white/10 rounded-2xl p-4 text-left space-y-2.5">
<div className="flex items-center space-x-2 text-white font-bold text-xs uppercase">
<QrCode className="w-4 h-4 text-white" />
<span>Paso 2: Realiza la separación de $10.000 COP</span>
</div>
<p className="text-xs text-zinc-300">
Paga de inmediato con <strong>Link de Pago Bold.co</strong> (PSE / Tarjeta / Nequi) o transfiere a nuestra cuenta oficial:
</p>
{boldLink && (
<a
href={boldLink}
target="_blank"
rel="noopener noreferrer"
className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-black text-xs py-2.5 px-3 rounded-xl flex items-center justify-center gap-1.5 shadow-md transition-all"
>
<CreditCard className="w-4 h-4" />
<span>Pagar $10.000 COP con Bold.co (PSE / Tarjetas)</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
)}
<div className="bg-zinc-900 p-2.5 rounded-xl border border-zinc-700 text-xs font-mono font-bold text-white">
📱 Nequi / Daviplata: <strong>{storeSettings?.nequiAccount || '316 745 5877'}</strong> ({storeSettings?.accountHolder || 'Carlos Andrés Lasso'})
</div>
<p className="text-[11px] text-emerald-400 font-semibold">
🤝 Cancelas el saldo de <strong>{formatCOP(orderCompleted.balanceDue)}</strong> al recibir el paquete en tu casa.
</p>
</div>
) : (
<div className="bg-zinc-950 border border-white/10 rounded-2xl p-4 text-left space-y-2">
<p className="text-xs text-zinc-300 font-semibold">
Tu pedido quedó programado para entrega contraentrega en 24 horas. ¡Nos pondremos en contacto contigo por WhatsApp!
</p>
</div>
)}
<div className="flex flex-col gap-2 pt-2">
<button
onClick={() => handleOpenWhatsAppOrder(orderCompleted)}
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-sm py-3 px-4 rounded-xl flex items-center justify-center space-x-2 shadow-lg shadow-emerald-600/20 active:scale-95 transition-all cursor-pointer"
>
<MessageCircle className="w-4 h-4 fill-white" />
<span>Confirmar y Enviar Comprobante por WhatsApp</span>
</button>
<Link
href="/"
className="w-full bg-zinc-800 hover:bg-zinc-700 text-zinc-300 font-bold text-xs py-2.5 px-4 rounded-xl text-center transition-all border border-zinc-700"
>
Volver a la Tienda
</Link>
</div>
</div>
</div>
);
}
return (
<div className={`min-h-screen ${currentBrand.colors.bodyBg} py-8 px-4 font-sans`}>
<div className="max-w-4xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<Link
href="/"
className="flex items-center space-x-2 text-xs font-bold text-zinc-400 hover:text-white transition-colors"
>
<ArrowLeft className="w-4 h-4" />
<span>Regresar a {currentBrand.name}</span>
</Link>
<div className="flex items-center space-x-1.5 text-xs font-bold text-white bg-white/10 border border-white/10 px-3 py-1 rounded-full shadow-sm">
<ShieldCheck className="w-4 h-4" />
<span>Compra Segura & Garantizada</span>
</div>
</div>
<form onSubmit={handleSubmitOrder} className="grid grid-cols-1 md:grid-cols-12 gap-6">
{/* Left Column: Shipping & Payment Details (7 cols) */}
<div className="md:col-span-7 space-y-5">
{/* 1. Datos del Cliente */}
<div className="bg-zinc-900 border border-zinc-800 rounded-3xl p-5 sm:p-6 shadow-sm space-y-4">
<h3 className="font-black text-white text-base flex items-center gap-2">
<User className="w-5 h-5 text-white" />
<span>1. Datos de Entrega</span>
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-bold text-zinc-300 mb-1">Nombre Completo *</label>
<input
type="text"
required
placeholder="Ej. Carlos Andrés Lasso"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2 text-sm text-white focus:border-orange-500 outline-hidden"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-300 mb-1">Celular / WhatsApp *</label>
<input
type="tel"
required
placeholder="Ej. 3167455877"
value={customerPhone}
onChange={(e) => setCustomerPhone(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2 text-sm text-white focus:border-orange-500 outline-hidden"
/>
</div>
</div>
{/* City Selection */}
<div>
<label className="block text-xs font-bold text-zinc-300 mb-1">Ciudad de Despacho *</label>
<select
value={selectedCityId}
onChange={(e) => setSelectedCityId(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2.5 text-sm text-white focus:border-orange-500 font-semibold outline-hidden"
>
{cities.map((city) => (
<option key={city.id} value={city.id} className="bg-zinc-900">
{city.name} — {city.shippingCost === 0 ? 'Envío Gratis' : formatCOP(city.shippingCost)} ({city.deliveryTime})
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-bold text-zinc-300 mb-1">Dirección de Entrega *</label>
<input
type="text"
required
placeholder="Calle, Carrera, No. Casa/Apto"
value={address}
onChange={(e) => setAddress(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2 text-sm text-white focus:border-orange-500 outline-hidden"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-300 mb-1">Barrio / Sector</label>
<input
type="text"
placeholder="Ej. San Fernando / Capri"
value={neighborhood}
onChange={(e) => setNeighborhood(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2 text-sm text-white focus:border-orange-500 outline-hidden"
/>
</div>
</div>
</div>
{/* 2. Método de Pago */}
<div className="bg-zinc-900 border border-zinc-800 rounded-3xl p-5 sm:p-6 shadow-sm space-y-4">
<h3 className="font-black text-white text-base flex items-center gap-2">
<Zap className="w-5 h-5 text-white" />
<span>2. Selecciona tu Método de Pago</span>
</h3>
<div className="space-y-2.5">
{paymentMethods.map((method) => {
const isSelected = selectedPaymentId === method.id;
return (
<div
key={method.id}
onClick={() => setSelectedPaymentId(method.id)}
className={`p-4 rounded-2xl border transition-all cursor-pointer space-y-1.5 ${
isSelected
? `border-orange-500 bg-orange-950/20 shadow-md`
: 'border-zinc-800 hover:border-zinc-700 bg-zinc-950'
}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div
className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
isSelected ? 'border-orange-500' : 'border-zinc-600'
}`}
>
{isSelected && <div className="w-2 h-2 rounded-full bg-orange-500" />}
</div>
<span className="font-black text-white text-sm">{method.name}</span>
</div>
{method.requiresDeposit && (
<span className="bg-white/10 text-white border border-white/20 text-[10px] font-black px-2 py-0.5 rounded-full uppercase">
Separa con $10.000
</span>
)}
</div>
<p className="text-xs text-zinc-400 pl-6">{method.description}</p>
{isSelected && (method.accountInfo || (method.type === 'bold' && boldLink)) && (
<div className="ml-6 mt-2 bg-zinc-900 p-3 rounded-xl border border-white/10 text-xs space-y-2 text-white">
{method.accountInfo && <p className="font-semibold">{method.accountInfo}</p>}
{method.type === 'bold' && boldLink && (
<a
href={boldLink}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold px-3 py-1.5 rounded-lg shadow-sm"
>
<ExternalLink className="w-3.5 h-3.5" />
<span>Abrir Link de Pago Bold.co</span>
</a>
)}
</div>
)}
</div>
);
})}
</div>
</div>
</div>
{/* Right Column: Summary (5 cols) */}
<div className="md:col-span-5 space-y-5">
<div className="bg-zinc-900 border border-zinc-800 rounded-3xl p-5 sm:p-6 shadow-sm space-y-4 sticky top-20">
<h3 className="font-black text-white text-base">Resumen del Pedido</h3>
{/* Items */}
<div className="space-y-3 max-h-60 overflow-y-auto pr-1">
{checkoutItems.map((item, i) => (
<div key={i} className="flex gap-3 bg-zinc-950 p-2.5 rounded-2xl border border-zinc-800">
<img src={item.image} alt="" className="w-14 h-14 rounded-xl object-cover bg-zinc-900 border border-zinc-700" />
<div className="flex-1 text-xs">
<h4 className="font-bold text-white line-clamp-1">{item.productName}</h4>
<p className="text-zinc-400">Talla: <strong className="text-white">{item.size}</strong> | Cant: {item.quantity}</p>
<p className={`font-black mt-0.5 text-transparent bg-clip-text bg-gradient-to-r ${currentBrand.colors.primaryGradient}`}>
{formatCOP(item.price * item.quantity)}
</p>
</div>
</div>
))}
</div>
{/* Breakdown */}
<div className="border-t border-zinc-800 pt-3 space-y-2 text-xs text-zinc-400">
<div className="flex justify-between">
<span>Subtotal:</span>
<span className="font-bold text-white">{formatCOP(subtotal)}</span>
</div>
<div className="flex justify-between">
<span>Envío ({activeCity.name}):</span>
<span className="font-bold text-white">
{shippingCost === 0 ? <span className="text-emerald-400 font-bold uppercase">Gratis</span> : formatCOP(shippingCost)}
</span>
</div>
<div className="flex justify-between text-base font-black text-white pt-2 border-t border-zinc-800">
<span>Total a Pagar:</span>
<span className={`text-transparent bg-clip-text bg-gradient-to-r ${currentBrand.colors.primaryGradient} text-lg`}>
{formatCOP(totalAmount)}
</span>
</div>
{depositAmount > 0 && (
<div className="bg-white/5 border border-white/10 rounded-xl p-2.5 text-xs text-white space-y-1">
<div className="flex justify-between font-bold text-white">
<span>Separación por Bold / Nequi / Daviplata:</span>
<span>{formatCOP(depositAmount)}</span>
</div>
<div className="flex justify-between font-black text-white pt-1 border-t border-white/10">
<span>Saldo a pagar al mensajero:</span>
<span>{formatCOP(balanceDue)}</span>
</div>
</div>
)}
<div className="bg-white/5 border border-white/10 rounded-xl p-2.5 text-xs text-zinc-300 space-y-1">
<div className="flex items-center space-x-1.5 font-bold text-white">
<ShieldCheck className={`w-3.5 h-3.5 ${activeCity.firstExchangeFree ? 'text-emerald-400' : 'text-orange-400'}`} />
<span>Garantía en {activeCity.name}:</span>
</div>
<p className="text-[11px] text-zinc-300">
{activeCity.firstExchangeFree
? '1er Cambio de Talla 100% GRATIS (cambios posteriores con flete estándar).'
: 'Cambios de talla 100% garantizados (los costos de envío son asumidos por el cliente).'}
</p>
</div>
</div>
<button
type="submit"
disabled={isSubmitting}
className={`w-full bg-gradient-to-r ${currentBrand.colors.primaryGradient} hover:opacity-95 text-white font-black text-sm py-3.5 px-4 rounded-2xl shadow-lg active:scale-95 transition-all cursor-pointer flex items-center justify-center space-x-2`}
>
{isSubmitting ? (
<span>Procesando Pedido...</span>
) : (
<>
<span>Confirmar Pedido Contraentrega</span>
<Truck className="w-4 h-4" />
</>
)}
</button>
<p className="text-[11px] text-zinc-500 text-center leading-tight">
Al confirmar, se guardará tu pedido y recibirás asistencia inmediata por WhatsApp oficial ({officialWhatsapp}) para el seguimiento de tu entrega en 24 horas.
</p>
</div>
</div>
</form>
</div>
</div>
);
}