1009 lines
48 KiB
TypeScript
1009 lines
48 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, AlertCircle } 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 { useAuth } from '@/context/AuthContext';
|
|
import { renderClientTemplate } from '@/lib/templates-client';
|
|
import confetti from 'canvas-confetti';
|
|
import { OptimizedImage } from '@/components/ui/OptimizedImage';
|
|
|
|
export default function CheckoutPage() {
|
|
const { currentBrand } = useBrand();
|
|
const { user } = useAuth();
|
|
|
|
const [customerName, setCustomerName] = useState('');
|
|
const [customerPhone, setCustomerPhone] = useState('');
|
|
const [address, setAddress] = useState('');
|
|
const [neighborhood, setNeighborhood] = useState('');
|
|
const [notes, setNotes] = useState('');
|
|
const [orderSource, setOrderSource] = useState<string>('web');
|
|
|
|
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, setCheckoutItems] = useState<Array<{
|
|
productId: string;
|
|
productName: string;
|
|
brand: string;
|
|
size: string;
|
|
quantity: number;
|
|
price: number;
|
|
image: string;
|
|
availableSizes?: string[];
|
|
}>>([]);
|
|
|
|
const handleUpdateItemSize = (index: number, newSize: string) => {
|
|
setCheckoutItems((prev) => {
|
|
const copy = [...prev];
|
|
if (copy[index]) {
|
|
copy[index] = { ...copy[index], size: newSize };
|
|
}
|
|
try {
|
|
localStorage.setItem('dripzone_cart', JSON.stringify(copy));
|
|
} catch (_) {}
|
|
return copy;
|
|
});
|
|
};
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [orderCompleted, setOrderCompleted] = useState<Order | null>(null);
|
|
const [userRequiresDeposit, setUserRequiresDeposit] = useState<boolean>(true);
|
|
const [userDepositReason, setUserDepositReason] = useState<string>('Garantía de separación requerida para el primer pedido');
|
|
const [showOtpModal, setShowOtpModal] = useState<boolean>(false);
|
|
const [otpCode, setOtpCode] = useState<string>('');
|
|
const [otpLoading, setOtpLoading] = useState<boolean>(false);
|
|
const [otpError, setOtpError] = useState<string>('');
|
|
|
|
// Estados para pasarela Bold & QR Bre-B
|
|
const [boldPayment, setBoldPayment] = useState<{
|
|
paymentLink: string;
|
|
checkoutUrl: string;
|
|
qrImageUrl: string;
|
|
amount: number;
|
|
environment?: string;
|
|
} | null>(null);
|
|
const [boldPaid, setBoldPaid] = useState(false);
|
|
const [boldLoading, setBoldLoading] = useState(false);
|
|
|
|
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(() => {});
|
|
|
|
// Carga dinámica de productos desde URL (WhatsApp Catalog / Asesor) o Carrito local
|
|
if (typeof window !== 'undefined') {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const urlProductId = params.get('productId') || params.get('id');
|
|
const urlSize = params.get('size');
|
|
const urlPrice = params.get('price') ? parseInt(params.get('price')!, 10) : undefined;
|
|
const urlName = params.get('name');
|
|
const urlSource = params.get('source');
|
|
|
|
if (urlSource === 'whatsapp' || urlProductId) {
|
|
setOrderSource('whatsapp');
|
|
}
|
|
|
|
if (urlProductId) {
|
|
fetch('/api/products?all=true')
|
|
.then((r) => r.json())
|
|
.then((d) => {
|
|
if (d.success && Array.isArray(d.products)) {
|
|
const matched = d.products.find((p: any) => p.id === urlProductId);
|
|
if (matched) {
|
|
const item = {
|
|
productId: matched.id,
|
|
productName: matched.name,
|
|
brand: matched.brand || 'Sneakers',
|
|
size: urlSize || (matched.sizes?.[0] || '39'),
|
|
quantity: 1,
|
|
price: urlPrice || matched.retailPrice || 125000,
|
|
image: matched.images?.[0] || matched.colors?.[0]?.image || '/splash.png',
|
|
availableSizes: matched.sizes || ['37', '38', '39', '40', '41', '42', '43'],
|
|
};
|
|
setCheckoutItems([item]);
|
|
localStorage.setItem('dripzone_cart', JSON.stringify([item]));
|
|
return;
|
|
}
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
} else if (urlName) {
|
|
const item = {
|
|
productId: `custom-${Date.now()}`,
|
|
productName: decodeURIComponent(urlName),
|
|
brand: 'DripZone',
|
|
size: urlSize || '39',
|
|
quantity: 1,
|
|
price: urlPrice || 125000,
|
|
image: params.get('image') ? decodeURIComponent(params.get('image')!) : '/splash.png',
|
|
availableSizes: ['37', '38', '39', '40', '41', '42', '43'],
|
|
};
|
|
setCheckoutItems([item]);
|
|
return;
|
|
}
|
|
|
|
// Si no viene en URL, intentar cargar de localStorage
|
|
try {
|
|
const stored = localStorage.getItem('dripzone_cart');
|
|
if (stored) {
|
|
const parsed = JSON.parse(stored);
|
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
setCheckoutItems(
|
|
parsed.map((item: any) => ({
|
|
productId: item.productId || item.product?.id || `item-${Date.now()}`,
|
|
productName: item.productName || item.product?.name || 'Zapatillas DripZone',
|
|
brand: item.brand || item.product?.brand || 'Sneakers',
|
|
size: item.size || item.selectedSize || '39',
|
|
quantity: item.quantity || 1,
|
|
price: item.price || item.product?.retailPrice || 125000,
|
|
image: item.image || item.product?.images?.[0] || '/splash.png',
|
|
availableSizes: item.availableSizes || item.product?.sizes || ['37', '38', '39', '40', '41', '42', '43'],
|
|
}))
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
|
|
// Fallback a productos activos
|
|
fetch('/api/products')
|
|
.then((r) => r.json())
|
|
.then((d) => {
|
|
if (d.success && d.products?.length > 0) {
|
|
const first = d.products[0];
|
|
setCheckoutItems([
|
|
{
|
|
productId: first.id,
|
|
productName: first.name,
|
|
brand: first.brand || 'Sneakers',
|
|
size: first.sizes?.[0] || '39',
|
|
quantity: 1,
|
|
price: first.retailPrice || 125000,
|
|
image: first.images?.[0] || first.colors?.[0]?.image || '/splash.png',
|
|
availableSizes: first.sizes || ['37', '38', '39', '40', '41', '42', '43'],
|
|
},
|
|
]);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
setUserRequiresDeposit((user.totalOrders || 0) === 0 || user.requiresShippingDeposit === true);
|
|
setUserDepositReason(user.depositReason || '');
|
|
}
|
|
}, [user]);
|
|
|
|
useEffect(() => {
|
|
const clean = customerPhone.replace(/\D/g, '');
|
|
if (clean.length >= 10) {
|
|
fetch(`/api/users?phone=${clean}`)
|
|
.then((r) => r.json())
|
|
.then((data) => {
|
|
if (data.success) {
|
|
setUserRequiresDeposit(data.requiresShippingDeposit !== false);
|
|
setUserDepositReason(data.depositReason || '');
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
}, [customerPhone]);
|
|
|
|
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;
|
|
|
|
// Lógica de Cobro:
|
|
// - Si el método es Bold 100% en línea (PSE, Tarjetas, Nequi, etc. sin separación): cobra el totalAmount.
|
|
// - Si el método requiere separación (ej. $10.000 COP) o el usuario la tiene obligatoria: cobra el anticipo ($10.000 COP) y el saldo queda contraentrega.
|
|
// - Si es 100% contraentrega en efectivo (ej. Cali): depósito $0 y saldo total contraentrega.
|
|
const is100PercentOnline = activePayment?.type === 'bold' && !activePayment?.requiresDeposit;
|
|
const isSeparation = Boolean(activePayment?.requiresDeposit || userRequiresDeposit);
|
|
const isMandatoryDeposit = isSeparation;
|
|
|
|
const depositAmount = is100PercentOnline
|
|
? totalAmount
|
|
: isSeparation
|
|
? (activePayment?.depositAmount || 10000)
|
|
: 0;
|
|
|
|
const balanceDue = Math.max(0, totalAmount - depositAmount);
|
|
|
|
const officialWhatsapp = storeSettings?.whatsappNumber || '573106390296';
|
|
const boldLink = activePayment?.boldPaymentLink || storeSettings?.boldPaymentLink || 'https://checkout.bold.co/payment/LNK_AJPFKOPB0C';
|
|
const boldEnv = storeSettings?.boldEnvironment || activePayment?.environment || 'production';
|
|
|
|
const processOrderSubmission = async () => {
|
|
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: notes ? `${notes} (Origen: ${orderSource})` : `Origen: ${orderSource}`,
|
|
source: orderSource,
|
|
};
|
|
|
|
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 },
|
|
});
|
|
|
|
// Si la orden requiere cobro en línea vía Bold (anticipo de separación o pago total 100%)
|
|
if (depositAmount > 0) {
|
|
setBoldLoading(true);
|
|
fetch('/api/bold/create-payment', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
orderId: data.order.id,
|
|
amount: depositAmount,
|
|
type: is100PercentOnline ? 'full' : 'deposit',
|
|
payerEmail: '',
|
|
redirectionUrl: window.location.href,
|
|
}),
|
|
})
|
|
.then((r) => r.json())
|
|
.then((bData) => {
|
|
if (bData.success) {
|
|
setBoldPayment(bData);
|
|
}
|
|
})
|
|
.catch((err) => console.error('Error generando cobro Bold:', err))
|
|
.finally(() => setBoldLoading(false));
|
|
}
|
|
} else {
|
|
alert(data.error || 'Error al registrar pedido.');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error creando orden:', err);
|
|
alert('Error de conexión al registrar pedido.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
// Escuchador en tiempo real del estado de pago en Bold
|
|
useEffect(() => {
|
|
if (!boldPayment?.paymentLink || boldPaid || !orderCompleted?.id) return;
|
|
|
|
const interval = setInterval(async () => {
|
|
try {
|
|
const res = await fetch(`/api/bold/status?linkId=${boldPayment.paymentLink}&orderId=${orderCompleted.id}`);
|
|
const data = await res.json();
|
|
if (data.success && data.isPaid) {
|
|
setBoldPaid(true);
|
|
confetti({
|
|
particleCount: 150,
|
|
spread: 90,
|
|
origin: { y: 0.5 },
|
|
});
|
|
}
|
|
} catch (_) {}
|
|
}, 3000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [boldPayment, boldPaid, orderCompleted]);
|
|
|
|
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;
|
|
}
|
|
const cleanPhone = customerPhone.replace(/\D/g, '');
|
|
if (cleanPhone.length < 10) {
|
|
alert('Por favor ingresa un número de celular/WhatsApp válido a 10 dígitos.');
|
|
return;
|
|
}
|
|
|
|
if (user?.phoneVerified && user.phone === cleanPhone) {
|
|
await processOrderSubmission();
|
|
return;
|
|
}
|
|
|
|
// Solicitar OTP por WhatsApp
|
|
setOtpLoading(true);
|
|
setOtpError('');
|
|
try {
|
|
const res = await fetch('/api/auth/send-otp', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ identifier: cleanPhone, name: customerName }),
|
|
});
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setShowOtpModal(true);
|
|
} else {
|
|
alert(data.error || 'No se pudo enviar el código OTP a WhatsApp.');
|
|
}
|
|
} catch (err) {
|
|
alert('Error al solicitar código OTP por WhatsApp.');
|
|
} finally {
|
|
setOtpLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleVerifyOtpAndPlaceOrder = async () => {
|
|
if (otpCode.length < 6) {
|
|
setOtpError('Ingresa el código de 6 dígitos enviado a tu WhatsApp.');
|
|
return;
|
|
}
|
|
setOtpLoading(true);
|
|
setOtpError('');
|
|
try {
|
|
const cleanPhone = customerPhone.replace(/\D/g, '');
|
|
const res = await fetch('/api/auth/verify-otp', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
identifier: cleanPhone,
|
|
code: otpCode,
|
|
name: customerName,
|
|
city: activeCity.name,
|
|
address: `${address} - Barrio: ${neighborhood || 'Centro'}`,
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setShowOtpModal(false);
|
|
await processOrderSubmission();
|
|
} else {
|
|
setOtpError(data.error || 'Código incorrecto o expirado.');
|
|
}
|
|
} catch (err) {
|
|
setOtpError('Error verificando código.');
|
|
} finally {
|
|
setOtpLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleOpenWhatsAppOrder = (order: Order) => {
|
|
const itemsSummary = order.items
|
|
.map((i) => `• ${i.productName} (Talla ${i.size}) x${i.quantity} = ${formatCOP(i.price * i.quantity)}`)
|
|
.join('\n');
|
|
|
|
const paymentCondition = order.depositAmount > 0
|
|
? `⚡ *Separación:* ${formatCOP(order.depositAmount)}\n🤝 *Saldo Contraentrega:* ${formatCOP(order.balanceDue)}`
|
|
: '🤝 *Pago 100% Contraentrega.*';
|
|
|
|
const rawText = renderClientTemplate('order_checkout_wa', {
|
|
tienda_nombre: currentBrand.name,
|
|
orden_id: order.id,
|
|
cliente_nombre: order.customerName,
|
|
cliente_telefono: order.customerPhone,
|
|
cliente_direccion: order.address,
|
|
cliente_ciudad: order.cityName,
|
|
articulos_resumen: itemsSummary,
|
|
total: formatCOP(order.totalAmount),
|
|
condicion_pago_resumen: paymentCondition,
|
|
metodo_pago: order.paymentMethodName,
|
|
saldo_contraentrega: formatCOP(order.balanceDue),
|
|
});
|
|
|
|
window.open(`https://wa.me/${officialWhatsapp}?text=${encodeURIComponent(rawText)}`, '_blank');
|
|
};
|
|
|
|
|
|
if (orderCompleted) {
|
|
return (
|
|
<div className={`min-h-screen ${currentBrand.colors.bodyBg} flex items-center justify-center p-4`}>
|
|
<div className={`bg-neutral-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-neutral-400 mt-1">
|
|
Hemos recibido tu solicitud para entrega en <strong>{orderCompleted.cityName}</strong> con <strong>{currentBrand.name}</strong>.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Bold.co QR Bre-B and Separation Box */}
|
|
{orderCompleted.depositAmount > 0 ? (
|
|
boldPaid ? (
|
|
<div className="bg-emerald-950/60 border border-emerald-500/50 rounded-2xl p-5 text-center space-y-3 shadow-lg animate-in zoom-in-95">
|
|
<div className="w-12 h-12 bg-emerald-500/20 text-emerald-400 rounded-full flex items-center justify-center mx-auto">
|
|
<CheckCircle2 className="w-7 h-7" />
|
|
</div>
|
|
<h4 className="text-base font-black text-emerald-300">¡Anticipo de $10.000 COP Confirmado con Éxito!</h4>
|
|
<p className="text-xs text-neutral-300 leading-relaxed">
|
|
Tu pago fue procesado correctamente mediante <strong>Bold</strong>. Tu orden ha sido autorizada para alistamiento prioritario en bodega y despacho inmediato.
|
|
</p>
|
|
<div className="bg-black/50 p-3 rounded-xl border border-emerald-500/30 text-xs flex justify-between items-center">
|
|
<span className="text-neutral-400">🤝 Saldo a Pagar Contraentrega:</span>
|
|
<strong className="text-emerald-400 font-mono text-sm">
|
|
{formatCOP(Math.max(0, orderCompleted.totalAmount - (orderCompleted.depositAmount || 10000)))} COP
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="bg-neutral-950 border border-purple-500/40 rounded-2xl p-4 sm:p-5 text-left space-y-3.5 shadow-xl">
|
|
<div className="flex items-center justify-between border-b border-white/10 pb-2.5">
|
|
<div className="flex items-center space-x-2 text-white font-bold text-xs uppercase">
|
|
<QrCode className="w-4 h-4 text-purple-400" />
|
|
<span>
|
|
{orderCompleted.balanceDue === 0
|
|
? `Paso 2: Pago en Línea 100% Seguro (${formatCOP(orderCompleted.totalAmount)})`
|
|
: `Paso 2: Anticipo de Separación (${formatCOP(orderCompleted.depositAmount || 10000)})`}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{boldPayment?.environment === 'sandbox' || boldEnv === 'sandbox' ? (
|
|
<span className="bg-amber-500/20 text-amber-300 border border-amber-500/40 text-[9px] px-2 py-0.5 rounded-full font-black flex items-center gap-1 shadow-xs">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-ping" />
|
|
SANDBOX (PRUEBAS)
|
|
</span>
|
|
) : (
|
|
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 text-[9px] px-2 py-0.5 rounded-full font-black flex items-center gap-1 shadow-xs">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
PRODUCCIÓN (LIVE)
|
|
</span>
|
|
)}
|
|
<span className="bg-purple-500/20 text-purple-300 border border-purple-500/40 text-[10px] px-2.5 py-0.5 rounded-full font-bold">
|
|
QR Bre-B / Bold
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-xs text-neutral-300 leading-relaxed">
|
|
Escanea el <strong>Código QR Bre-B</strong> o toca el botón para pagar en línea de forma 100% segura con <strong>Bold</strong>:
|
|
</p>
|
|
|
|
{boldPayment?.qrImageUrl ? (
|
|
<div className="flex flex-col items-center justify-center gap-2 bg-white p-4 rounded-2xl shadow-md border-2 border-purple-500/30">
|
|
<img
|
|
src={boldPayment.qrImageUrl}
|
|
alt="Código QR Bre-B Bold"
|
|
className="w-48 h-48 sm:w-56 sm:h-56 object-contain rounded-lg"
|
|
/>
|
|
<div className="text-center space-y-0.5">
|
|
<p className="text-xs font-black text-neutral-900">
|
|
Total a Pagar: {formatCOP(orderCompleted.depositAmount || (orderCompleted.balanceDue === 0 ? orderCompleted.totalAmount : 10000))} COP
|
|
</p>
|
|
<p className="text-[10px] font-semibold text-neutral-600">
|
|
⚡ Compatible con Bre-B e Interoperabilidad Bancaria
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : boldLoading ? (
|
|
<div className="py-8 text-center text-xs text-purple-400 font-bold animate-pulse">
|
|
Generando Código QR Bre-B con Bold...
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="space-y-2">
|
|
{boldPayment?.checkoutUrl && (
|
|
<a
|
|
href={boldPayment.checkoutUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="w-full bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white font-black text-xs py-3 px-4 rounded-xl flex items-center justify-center gap-2 shadow-lg transition-all"
|
|
>
|
|
<CreditCard className="w-4 h-4" />
|
|
<span>
|
|
Pagar {formatCOP(orderCompleted.depositAmount || (orderCompleted.balanceDue === 0 ? orderCompleted.totalAmount : 10000))} con PSE, Tarjetas, Nequi o Bre-B
|
|
</span>
|
|
<ExternalLink className="w-3.5 h-3.5" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
|
|
<div className="pt-2 border-t border-white/10 flex justify-between items-center text-xs">
|
|
<span className="text-neutral-400">🤝 Saldo restante contraentrega:</span>
|
|
<span className="text-emerald-400 font-bold font-mono">
|
|
{orderCompleted.balanceDue === 0
|
|
? '$0 COP (Despacho Prioritario)'
|
|
: `${formatCOP(orderCompleted.balanceDue || Math.max(0, orderCompleted.totalAmount - (orderCompleted.depositAmount || 10000)))} COP`}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-center gap-1.5 text-[10px] text-purple-300/80 font-medium text-center">
|
|
<div className="w-2 h-2 rounded-full bg-purple-400 animate-ping" />
|
|
<span>Esperando confirmación en tiempo real...</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
) : (
|
|
<div className="bg-neutral-950 border border-white/10 rounded-2xl p-4 text-left space-y-2">
|
|
<p className="text-xs text-neutral-300 font-semibold">
|
|
¡Cliente Frecuente! Tu pedido quedó programado con 100% Pago Contraentrega al recibir en tu puerta.
|
|
</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-neutral-800 hover:bg-neutral-700 text-neutral-300 font-bold text-xs py-2.5 px-4 rounded-xl text-center transition-all border border-neutral-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-neutral-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>
|
|
|
|
{/* Banner de Pedido Originado en WhatsApp */}
|
|
{orderSource === 'whatsapp' && (
|
|
<div className="p-4 sm:p-5 rounded-3xl bg-gradient-to-r from-emerald-950/70 via-neutral-900 to-neutral-950 border border-emerald-500/40 shadow-xl flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
<div className="flex items-start gap-3.5">
|
|
<div className="w-10 h-10 rounded-2xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center shrink-0 border border-emerald-500/30 mt-0.5">
|
|
<MessageCircle className="w-5 h-5" />
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<h4 className="text-sm font-black text-white">¡Pedido iniciado desde WhatsApp!</h4>
|
|
<span className="text-[10px] bg-emerald-500/20 text-emerald-400 border border-emerald-500/40 px-2 py-0.5 rounded-full font-bold uppercase tracking-wider">
|
|
Atención Comercial
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-neutral-300 leading-relaxed">
|
|
Tu calzado y talla seleccionada están reservados. Completa tu dirección y confirma el anticipo de <strong className="text-emerald-400">$10.000 en línea</strong> (PSE, Tarjetas Débito/Crédito, Nequi o Bre-B) para asegurar tu despacho hoy. El saldo restante lo pagas <strong>contraentrega (en efectivo o transferencia)</strong> al recibir en tu puerta.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<a
|
|
href="/app-dripzone.apk"
|
|
download="app-dripzone.apk"
|
|
className="px-3.5 py-2 bg-neutral-900 hover:bg-neutral-800 border border-neutral-700 hover:border-purple-500/50 text-neutral-300 hover:text-white rounded-xl text-xs font-bold flex items-center gap-1.5 shrink-0 transition-all self-start sm:self-auto"
|
|
>
|
|
<Zap className="w-3.5 h-3.5 text-purple-400" />
|
|
<span>O pedir en App Móvil</span>
|
|
<ExternalLink className="w-3 h-3 text-neutral-500" />
|
|
</a>
|
|
</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-neutral-900 border border-neutral-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-neutral-300 mb-1">Nombre Completo *</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
placeholder="Ej: Juan Pérez"
|
|
value={customerName}
|
|
onChange={(e) => setCustomerName(e.target.value)}
|
|
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-sm text-white focus:border-purple-400 outline-hidden"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-neutral-300 mb-1">Celular / WhatsApp *</label>
|
|
<input
|
|
type="tel"
|
|
required
|
|
placeholder="Ej: 3XX XXX XXXX"
|
|
value={customerPhone}
|
|
onChange={(e) => setCustomerPhone(e.target.value)}
|
|
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-sm text-white focus:border-purple-400 outline-hidden"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* City Selection */}
|
|
<div>
|
|
<label className="block text-xs font-bold text-neutral-300 mb-1">Ciudad de Despacho *</label>
|
|
<select
|
|
value={selectedCityId}
|
|
onChange={(e) => setSelectedCityId(e.target.value)}
|
|
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2.5 text-sm text-white focus:border-purple-400 font-semibold outline-hidden"
|
|
>
|
|
{cities.map((city) => (
|
|
<option key={city.id} value={city.id} className="bg-neutral-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-neutral-300 mb-1">Dirección de Entrega *</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
placeholder="Ej: Calle 123 # 45 - 67, Apto 201"
|
|
value={address}
|
|
onChange={(e) => setAddress(e.target.value)}
|
|
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-sm text-white focus:border-purple-400 outline-hidden"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-neutral-300 mb-1">Barrio / Sector</label>
|
|
<input
|
|
type="text"
|
|
placeholder="Ej: Barrio San Fernando / Casa blanca"
|
|
value={neighborhood}
|
|
onChange={(e) => setNeighborhood(e.target.value)}
|
|
className="w-full bg-neutral-950 border border-neutral-800 rounded-xl px-3 py-2 text-sm text-white focus:border-purple-400 outline-hidden"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* 2. Método de Pago */}
|
|
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-5 sm:p-6 shadow-sm space-y-4">
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
<h3 className="font-black text-white text-base flex items-center gap-2">
|
|
<Zap className="w-5 h-5 text-purple-400" />
|
|
<span>2. Selecciona tu Método de Pago</span>
|
|
</h3>
|
|
|
|
{boldEnv === 'sandbox' ? (
|
|
<span className="bg-amber-500/20 text-amber-300 border border-amber-500/40 text-[10px] font-black px-2.5 py-1 rounded-full flex items-center gap-1.5 shadow-sm w-fit">
|
|
<span className="w-2 h-2 rounded-full bg-amber-400 animate-ping shrink-0" />
|
|
<span>MODO PRUEBAS (SANDBOX)</span>
|
|
</span>
|
|
) : (
|
|
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 text-[10px] font-black px-2.5 py-1 rounded-full flex items-center gap-1.5 shadow-sm w-fit">
|
|
<span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
|
|
<span>PRODUCCIÓN (LIVE)</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{boldEnv === 'sandbox' && (
|
|
<div className="bg-amber-950/40 border border-amber-500/30 rounded-2xl p-3 flex items-start gap-2.5 text-xs text-amber-200">
|
|
<AlertCircle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
|
<div>
|
|
<p className="font-bold text-amber-300">Pasarela Bold en Modo Pruebas Activo</p>
|
|
<p className="text-[11px] text-amber-200/80 mt-0.5">Estás en entorno Sandbox. Los pagos generados con Bold son de prueba y no debitarán dinero real de tus cuentas.</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2.5">
|
|
{paymentMethods.map((method) => {
|
|
const isSelected = selectedPaymentId === method.id;
|
|
const isBold = method.type === 'bold' || Boolean(method.boldChannel);
|
|
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-purple-400 bg-purple-950/20 shadow-md`
|
|
: 'border-neutral-800 hover:border-neutral-700 bg-neutral-950'
|
|
}`}
|
|
>
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
<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-purple-400' : 'border-neutral-600'
|
|
}`}
|
|
>
|
|
{isSelected && <div className="w-2 h-2 rounded-full bg-purple-400" />}
|
|
</div>
|
|
<span className="font-black text-white text-sm">{method.name}</span>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-1.5 pl-6 sm:pl-0">
|
|
{/* IDENTIFICADOR DE ENTORNO EN CADA MÉTODO */}
|
|
{isBold && (
|
|
boldEnv === 'sandbox' ? (
|
|
<span className="bg-amber-500/20 text-amber-300 border border-amber-500/40 text-[9px] font-black px-2 py-0.5 rounded-full flex items-center gap-1">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
|
|
PRUEBAS SANDBOX
|
|
</span>
|
|
) : (
|
|
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 text-[9px] font-black px-2 py-0.5 rounded-full flex items-center gap-1">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
EN VIVO LIVE
|
|
</span>
|
|
)
|
|
)}
|
|
|
|
{method.badge ? (
|
|
<span className="bg-indigo-500/20 text-indigo-300 border border-indigo-500/30 text-[10px] font-black px-2 py-0.5 rounded-full uppercase">
|
|
{method.badge}
|
|
</span>
|
|
) : 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 {formatCOP(method.depositAmount || 10000)}
|
|
</span>
|
|
) : method.type === 'bold' ? (
|
|
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-[10px] font-bold px-2 py-0.5 rounded-full uppercase">
|
|
100% en Línea
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-xs text-neutral-400 pl-6">{method.description}</p>
|
|
|
|
{isSelected && (method.accountInfo || (method.type === 'bold' && boldLink)) && (
|
|
<div className="ml-6 mt-2 bg-neutral-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' && (method.boldPaymentLink || boldLink) && (
|
|
<a
|
|
href={method.boldPaymentLink || 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>Pagar en Línea con Bold (PSE, Tarjetas, Nequi o Bre-B)</span>
|
|
</a>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Column: Summary (5 cols) */}
|
|
<div className="md:col-span-5 space-y-5">
|
|
<div className="bg-neutral-900 border border-neutral-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-80 overflow-y-auto pr-1">
|
|
{checkoutItems.map((item, i) => (
|
|
<div key={i} className="flex flex-col gap-2.5 bg-neutral-950 p-3 rounded-2xl border border-neutral-800">
|
|
<div className="flex gap-3">
|
|
<OptimizedImage
|
|
src={item.image}
|
|
alt={item.productName}
|
|
width={120}
|
|
height={120}
|
|
quality={75}
|
|
className="w-14 h-14 rounded-xl object-cover bg-neutral-900 border border-neutral-700 shrink-0"
|
|
containerClassName="w-14 h-14 rounded-xl shrink-0"
|
|
/>
|
|
<div className="flex-1 text-xs">
|
|
<h4 className="font-bold text-white line-clamp-1">{item.productName}</h4>
|
|
<p className="text-neutral-400">Talla elegida: <strong className="text-white font-mono">{item.size}</strong></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>
|
|
|
|
{/* Selector de tallas interactivo para clientes desde WhatsApp */}
|
|
{item.availableSizes && item.availableSizes.length > 0 && (
|
|
<div className="pt-2 border-t border-neutral-900">
|
|
<span className="text-[10px] text-neutral-400 font-bold uppercase tracking-wider block mb-1.5">
|
|
📏 Selecciona o Cambia tu Talla:
|
|
</span>
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{item.availableSizes.map((sz) => (
|
|
<button
|
|
type="button"
|
|
key={sz}
|
|
onClick={() => handleUpdateItemSize(i, sz)}
|
|
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition-all cursor-pointer ${
|
|
item.size === sz
|
|
? 'bg-purple-600 text-white shadow-md shadow-purple-600/30 font-black scale-105 border border-purple-400'
|
|
: 'bg-neutral-900 hover:bg-neutral-800 text-neutral-300 border border-neutral-700'
|
|
}`}
|
|
>
|
|
{sz}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Breakdown */}
|
|
<div className="border-t border-neutral-800 pt-3 space-y-2 text-xs text-neutral-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-sm font-bold text-white pt-2 border-t border-neutral-800">
|
|
<span>Total de la Orden:</span>
|
|
<span className="font-bold text-white">
|
|
{formatCOP(totalAmount)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Discriminación de Separación y Saldo Contraentrega */}
|
|
<div className="bg-neutral-950 border border-white/10 rounded-2xl p-3.5 space-y-2 text-xs">
|
|
<div className="flex justify-between items-center text-xs">
|
|
<span className="text-purple-300 font-bold">
|
|
{isMandatoryDeposit ? '⚡ Valor Separación (1er Pedido):' : '⭐ Separación (Cliente Frecuente):'}
|
|
</span>
|
|
<span className="font-black text-white font-mono">
|
|
{isMandatoryDeposit ? formatCOP(depositAmount) : '$0 COP (Exento)'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center pt-2 border-t border-white/10 text-xs">
|
|
<span className="text-emerald-400 font-black">🤝 Saldo a Pagar Contraentrega:</span>
|
|
<span className="font-mono font-black text-emerald-400 text-sm">{formatCOP(balanceDue)}</span>
|
|
</div>
|
|
|
|
<p className="text-[10px] text-neutral-400 pt-1 leading-relaxed">
|
|
{isMandatoryDeposit
|
|
? `Para tu primer pedido aseguras el despacho de tu orden (${checkoutItems.reduce((acc, it) => acc + it.quantity, 0)} items) con una separación fija única de $10.000 COP vía Bold y cancelas el saldo restante en efectivo al recibir tu paquete.`
|
|
: '¡Gracias por tu confianza! Como cliente frecuente, cancelas el 100% del pedido contraentrega al recibir.'}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="bg-white/5 border border-white/10 rounded-xl p-2.5 text-xs text-neutral-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-purple-400'}`} />
|
|
<span>Garantía en {activeCity.name}:</span>
|
|
</div>
|
|
<p className="text-[11px] text-neutral-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>
|
|
) : isMandatoryDeposit ? (
|
|
<>
|
|
<span>Pagar Separación ($10.000 COP)</span>
|
|
<CreditCard className="w-4 h-4" />
|
|
</>
|
|
) : (
|
|
<>
|
|
<span>Enviar Pedido (100% Contraentrega)</span>
|
|
<Truck className="w-4 h-4" />
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
<p className="text-[11px] text-neutral-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>
|
|
|
|
{showOtpModal && (
|
|
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 animate-in fade-in">
|
|
<div className="bg-neutral-900 border border-purple-500/40 rounded-3xl max-w-md w-full p-6 text-white space-y-4 shadow-2xl">
|
|
<div className="flex justify-between items-center border-b border-neutral-800 pb-3">
|
|
<h3 className="text-base font-black flex items-center gap-2">
|
|
<ShieldCheck className="w-5 h-5 text-purple-400" />
|
|
Verificación de WhatsApp (OTP)
|
|
</h3>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowOtpModal(false)}
|
|
className="text-neutral-400 hover:text-white text-sm cursor-pointer p-1"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
<p className="text-xs text-neutral-300 leading-relaxed">
|
|
Para validar tu cuenta y registrar tu orden contraentrega en la plataforma, te enviamos un código de seguridad de 6 dígitos a tu WhatsApp <strong>+57 {customerPhone}</strong>:
|
|
</p>
|
|
<input
|
|
type="text"
|
|
maxLength={6}
|
|
value={otpCode}
|
|
onChange={(e) => setOtpCode(e.target.value.replace(/\D/g, ''))}
|
|
placeholder="• • • • • •"
|
|
className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-4 py-3 text-center text-2xl font-mono tracking-widest text-white outline-none"
|
|
/>
|
|
{otpError && <p className="text-xs text-rose-400 text-center">{otpError}</p>}
|
|
<button
|
|
type="button"
|
|
disabled={otpLoading || otpCode.length < 6}
|
|
onClick={handleVerifyOtpAndPlaceOrder}
|
|
className="w-full bg-gradient-to-r from-purple-600 to-fuchsia-600 text-white font-bold py-3.5 rounded-xl hover:opacity-95 disabled:opacity-50 text-sm shadow-md transition-all cursor-pointer"
|
|
>
|
|
{otpLoading ? 'Verificando...' : 'Confirmar Código y Registrar Pedido'}
|
|
</button>
|
|
<p className="text-[11px] text-neutral-500 text-center">
|
|
El código expira en 5 minutos.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|