286 lines
12 KiB
TypeScript
286 lines
12 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import type { ReactElement } from 'react';
|
|
import Head from 'next/head';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/router';
|
|
import { mdiCart, mdiCheckCircle, mdiAlertCircle, mdiArrowLeft, mdiCreditCard, mdiShopping } from '@mdi/js';
|
|
import BaseButton from '../components/BaseButton';
|
|
import LayoutGuest from '../layouts/Guest';
|
|
import { getPageTitle } from '../config';
|
|
import BaseIcon from '../components/BaseIcon';
|
|
import FormField from '../components/FormField';
|
|
import axios from 'axios';
|
|
import Logo from '../components/Logo';
|
|
|
|
export default function Checkout() {
|
|
const router = useRouter();
|
|
const [cart, setCart] = useState<any[]>([]);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const [formData, setFormData] = useState({
|
|
customer_name: '',
|
|
customer_email: '',
|
|
customer_phone: '',
|
|
address_line1: '',
|
|
city: '',
|
|
notes: '',
|
|
});
|
|
|
|
useEffect(() => {
|
|
const savedCart = localStorage.getItem('cart');
|
|
if (savedCart) {
|
|
try {
|
|
const parsed = JSON.parse(savedCart);
|
|
setCart(parsed);
|
|
if (parsed.length === 0) {
|
|
router.push('/');
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to parse cart', e);
|
|
router.push('/');
|
|
}
|
|
} else {
|
|
router.push('/');
|
|
}
|
|
}, [router]);
|
|
|
|
const cartTotal = cart.reduce((acc, item) => acc + item.product.price * item.quantity, 0);
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setIsSubmitting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const orderNumber = `ORD-${Math.floor(100000 + Math.random() * 900000)}`;
|
|
|
|
const orderData = {
|
|
order_number: orderNumber,
|
|
customer_note: `Name: ${formData.customer_name}, Email: ${formData.customer_email}, Phone: ${formData.customer_phone}, Address: ${formData.address_line1}, ${formData.city}. Notes: ${formData.notes}`,
|
|
subtotal: cartTotal,
|
|
total: cartTotal,
|
|
status: 'pending_payment',
|
|
payment_method: 'card',
|
|
payment_status: 'authorized',
|
|
};
|
|
|
|
await axios.post('/orders', { data: orderData });
|
|
|
|
localStorage.removeItem('cart');
|
|
setIsSuccess(true);
|
|
|
|
} catch (err: any) {
|
|
console.error('Checkout failed', err);
|
|
setError('Hubo un problema al procesar tu pedido. Por favor intenta de nuevo.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
if (isSuccess) {
|
|
return (
|
|
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-dark-900">
|
|
<Head><title>{getPageTitle('Pedido Exitoso')}</title></Head>
|
|
<header className="bg-white dark:bg-dark-800 border-b border-gray-200 dark:border-dark-700 h-16 flex items-center">
|
|
<div className="container mx-auto px-4">
|
|
<Link href="/" className="flex items-center gap-2">
|
|
<Logo className="w-auto h-10" />
|
|
<span className="text-2xl font-extrabold text-emerald-600 hidden sm:inline">Tienda Virtual</span>
|
|
</Link>
|
|
</div>
|
|
</header>
|
|
<main className="flex-grow container mx-auto px-4 py-20 text-center">
|
|
<BaseIcon path={mdiCheckCircle} size={80} className="text-emerald-500 mx-auto mb-6" />
|
|
<h1 className="text-3xl font-extrabold mb-4">¡Gracias por tu compra!</h1>
|
|
<p className="text-xl text-gray-500 mb-8">Tu pedido ha sido recibido y está siendo procesado.</p>
|
|
<BaseButton label="Volver a la tienda" color="success" onClick={() => router.push('/')} icon={mdiArrowLeft} />
|
|
</main>
|
|
<footer className="bg-white dark:bg-dark-800 py-12 border-t border-gray-200 dark:border-dark-700">
|
|
<div className="container mx-auto px-4 text-center">
|
|
<p className="text-gray-500">© 2026 Tienda Virtual. Todos los derechos reservados.</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-dark-900">
|
|
<Head>
|
|
<title>{getPageTitle('Tienda Virtual - Finalizar Compra')}</title>
|
|
</Head>
|
|
|
|
<header className="bg-white dark:bg-dark-800 border-b border-gray-200 dark:border-dark-700 h-16 flex items-center sticky top-0 z-50">
|
|
<div className="container mx-auto px-4">
|
|
<Link href="/" className="flex items-center gap-2">
|
|
<Logo className="w-auto h-10" />
|
|
<span className="text-2xl font-extrabold text-emerald-600 hidden sm:inline">Tienda Virtual</span>
|
|
</Link>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="flex-grow container mx-auto px-4 py-12">
|
|
<div className="flex flex-col lg:flex-row gap-12">
|
|
{/* Checkout Form */}
|
|
<div className="flex-grow">
|
|
<h2 className="text-3xl font-extrabold mb-8 flex items-center gap-2">
|
|
<BaseButton icon={mdiArrowLeft} color="white" onClick={() => router.push('/')} className="mr-2" />
|
|
Información de Envío
|
|
</h2>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField label="Nombre Completo" help="Como aparece en tu identificación">
|
|
<input
|
|
name="customer_name"
|
|
value={formData.customer_name}
|
|
onChange={handleInputChange}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="Juan Pérez"
|
|
/>
|
|
</FormField>
|
|
<FormField label="Correo Electrónico">
|
|
<input
|
|
name="customer_email"
|
|
type="email"
|
|
value={formData.customer_email}
|
|
onChange={handleInputChange}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="juan@ejemplo.com"
|
|
/>
|
|
</FormField>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField label="Teléfono">
|
|
<input
|
|
name="customer_phone"
|
|
value={formData.customer_phone}
|
|
onChange={handleInputChange}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="+1 234 567 890"
|
|
/>
|
|
</FormField>
|
|
<FormField label="Ciudad">
|
|
<input
|
|
name="city"
|
|
value={formData.city}
|
|
onChange={handleInputChange}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="Ciudad de México"
|
|
/>
|
|
</FormField>
|
|
</div>
|
|
|
|
<FormField label="Dirección de Entrega">
|
|
<input
|
|
name="address_line1"
|
|
value={formData.address_line1}
|
|
onChange={handleInputChange}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="Calle, número, colonia..."
|
|
/>
|
|
</FormField>
|
|
|
|
<FormField label="Notas Adicionales (Opcional)">
|
|
<textarea
|
|
name="notes"
|
|
value={formData.notes}
|
|
onChange={handleInputChange}
|
|
rows={3}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none transition-all dark:bg-dark-800 dark:border-dark-700"
|
|
placeholder="Instrucciones especiales para el repartidor"
|
|
/>
|
|
</FormField>
|
|
|
|
<div className="bg-emerald-50 dark:bg-dark-800/50 p-6 rounded-xl border border-emerald-100 dark:border-emerald-900">
|
|
<h3 className="text-lg font-bold mb-4 flex items-center gap-2 text-emerald-700 dark:text-emerald-400">
|
|
<BaseIcon path={mdiCreditCard} /> Método de Pago
|
|
</h3>
|
|
<div className="flex items-center gap-4 p-4 bg-white dark:bg-dark-800 rounded-lg border-2 border-emerald-500">
|
|
<input type="radio" checked readOnly className="text-emerald-500" />
|
|
<span className="font-bold flex-grow">Tarjeta de Crédito / Débito (Simulado)</span>
|
|
<div className="flex gap-2">
|
|
<span className="bg-gray-100 dark:bg-dark-700 px-2 py-1 rounded text-xs">VISA</span>
|
|
<span className="bg-gray-100 dark:bg-dark-700 px-2 py-1 rounded text-xs">Mastercard</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="p-4 bg-red-50 text-red-600 rounded-lg flex items-center gap-2">
|
|
<BaseIcon path={mdiAlertCircle} /> {error}
|
|
</div>
|
|
)}
|
|
|
|
<BaseButton
|
|
type="submit"
|
|
label={isSubmitting ? "Procesando..." : `Confirmar y Pagar $${cartTotal.toFixed(2)}`}
|
|
color="success"
|
|
className="w-full rounded-xl py-4 text-xl font-bold"
|
|
disabled={isSubmitting}
|
|
/>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Order Summary */}
|
|
<div className="w-full lg:w-1/3">
|
|
<div className="bg-white dark:bg-dark-800 p-8 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700 sticky top-24">
|
|
<h3 className="text-xl font-bold mb-6">Resumen del Pedido</h3>
|
|
<div className="space-y-4 mb-8">
|
|
{cart.map((item) => (
|
|
<div key={item.product.id} className="flex justify-between items-center text-sm">
|
|
<div className="flex items-center gap-3">
|
|
<span className="bg-gray-100 dark:bg-dark-700 px-2 py-1 rounded font-bold text-xs">{item.quantity}x</span>
|
|
<span className="font-medium truncate max-w-[150px]">{item.product.name}</span>
|
|
</div>
|
|
<span className="font-bold">${(item.product.price * item.quantity).toFixed(2)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="space-y-2 border-t pt-4 mb-6">
|
|
<div className="flex justify-between text-gray-500">
|
|
<span>Subtotal</span>
|
|
<span>${cartTotal.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-gray-500">
|
|
<span>Envío</span>
|
|
<span className="text-emerald-600 font-bold">GRATIS</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center text-2xl font-extrabold text-emerald-600 border-t pt-4">
|
|
<span>Total</span>
|
|
<span>${cartTotal.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<footer className="bg-white dark:bg-dark-800 py-12 border-t border-gray-200 dark:border-dark-700">
|
|
<div className="container mx-auto px-4 text-center">
|
|
<p className="text-gray-500">© 2026 Tienda Virtual. Todos los derechos reservados.</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
Checkout.getLayout = function getLayout(page: ReactElement) {
|
|
return <LayoutGuest>{page}</LayoutGuest>;
|
|
};
|