481 lines
22 KiB
TypeScript
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>
|
|
);
|
|
}
|