diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx new file mode 100644 index 0000000..f4e1ce1 --- /dev/null +++ b/src/app/checkout/page.tsx @@ -0,0 +1,448 @@ +"use client" + +import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; +import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; +import { useState } from 'react'; +import { ShoppingCart, Trash2, Plus, Minus } from 'lucide-react'; +import FooterBaseReveal from '@/components/sections/footer/FooterBaseReveal'; + +interface CartItem { + id: string; + name: string; + price: string; + quantity: number; + imageSrc: string; + imageAlt: string; +} + +interface PaymentFormData { + cardName: string; + cardNumber: string; + expiryDate: string; + cvv: string; + email: string; + phone: string; +} + +export default function CheckoutPage() { + const [cartItems, setCartItems] = useState([ + { + id: "tres-leches", name: "Tarta Tres Leches", price: "€6.50", quantity: 1, + imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AwJRpdoUBlhmoBlz25LKt9jMJl/uploaded-1773500286117-4ambvo9k.png", imageAlt: "Tres Leches cake slice" + }, + { + id: "specialty-coffee", name: "Frappuccino de Galleta", price: "€3.50", quantity: 2, + imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AwJRpdoUBlhmoBlz25LKt9jMJl/uploaded-1773500356599-f2pk6pu9.png?_wi=1", imageAlt: "Specialty coffee drink" + } + ]); + + const [paymentFormData, setPaymentFormData] = useState({ + cardName: '', + cardNumber: '', + expiryDate: '', + cvv: '', + email: '', + phone: '' + }); + + const [isProcessing, setIsProcessing] = useState(false); + const [paymentStatus, setPaymentStatus] = useState<'idle' | 'success' | 'error'>('idle'); + const [errorMessage, setErrorMessage] = useState(''); + + const extractNumericPrice = (priceStr: string): number => { + const match = priceStr.match(/\d+(\.\d+)?/); + return match ? parseFloat(match[0]) : 0; + }; + + const subtotal = cartItems.reduce((sum, item) => { + return sum + (extractNumericPrice(item.price) * item.quantity); + }, 0); + + const tax = subtotal * 0.1; + const total = subtotal + tax; + + const handleQuantityChange = (id: string, newQuantity: number) => { + if (newQuantity <= 0) return; + setCartItems(cartItems.map(item => + item.id === id ? { ...item, quantity: newQuantity } : item + )); + }; + + const handleRemoveItem = (id: string) => { + setCartItems(cartItems.filter(item => item.id !== id)); + }; + + const handlePaymentInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setPaymentFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const validatePaymentForm = (): boolean => { + if (!paymentFormData.cardName.trim()) { + setErrorMessage('Cardholder name is required'); + return false; + } + if (!paymentFormData.cardNumber.replace(/\s/g, '').match(/^\d{13,19}$/)) { + setErrorMessage('Invalid card number'); + return false; + } + if (!paymentFormData.expiryDate.match(/^\d{2}\/\d{2}$/)) { + setErrorMessage('Expiry date must be MM/YY format'); + return false; + } + if (!paymentFormData.cvv.match(/^\d{3,4}$/)) { + setErrorMessage('Invalid CVV'); + return false; + } + if (!paymentFormData.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) { + setErrorMessage('Invalid email address'); + return false; + } + if (!paymentFormData.phone.trim()) { + setErrorMessage('Phone number is required'); + return false; + } + return true; + }; + + const handlePaymentSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setErrorMessage(''); + setPaymentStatus('idle'); + + if (!validatePaymentForm()) { + setPaymentStatus('error'); + return; + } + + if (cartItems.length === 0) { + setErrorMessage('Your cart is empty'); + setPaymentStatus('error'); + return; + } + + setIsProcessing(true); + + try { + const response = await fetch('/api/payment/process', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + paymentMethod: 'card', + cardName: paymentFormData.cardName, + cardNumber: paymentFormData.cardNumber.replace(/\s/g, ''), + expiryDate: paymentFormData.expiryDate, + cvv: paymentFormData.cvv, + email: paymentFormData.email, + phone: paymentFormData.phone, + amount: total, + currency: 'EUR', + items: cartItems, + orderDetails: { + subtotal: subtotal.toFixed(2), + tax: tax.toFixed(2), + total: total.toFixed(2) + } + }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || 'Payment processing failed'); + } + + const data = await response.json(); + setPaymentStatus('success'); + setCartItems([]); + setPaymentFormData({ cardName: '', cardNumber: '', expiryDate: '', cvv: '', email: '', phone: '' }); + } catch (error) { + setErrorMessage(error instanceof Error ? error.message : 'Payment processing failed. Please try again.'); + setPaymentStatus('error'); + } finally { + setIsProcessing(false); + } + }; + + return ( + + + +
+
+
+

+ + Checkout +

+

Review your order and complete payment

+
+ +
+ {/* Cart Items Section */} +
+
+

Order Summary

+ + {cartItems.length === 0 ? ( +
+ +

Your cart is empty

+
+ ) : ( +
+ {cartItems.map(item => ( +
+ {item.imageAlt} +
+

{item.name}

+

{item.price}

+
+ + {item.quantity} + +
+
+
+

+ €{(extractNumericPrice(item.price) * item.quantity).toFixed(2)} +

+ +
+
+ ))} +
+ )} +
+
+ + {/* Payment Form and Summary */} +
+ {/* Order Totals */} +
+

Order Total

+
+
+ Subtotal: + €{subtotal.toFixed(2)} +
+
+ Tax (10%): + €{tax.toFixed(2)} +
+
+
+ Total: + €{total.toFixed(2)} +
+
+ + {/* Payment Form */} +
+

Payment Details

+ + {paymentStatus === 'success' && ( +
+ ✓ Payment processed successfully! Your order has been received. +
+ )} + + {paymentStatus === 'error' && errorMessage && ( +
+ ✗ {errorMessage} +
+ )} + +
+
+ + +
+ +
+ + { + const value = e.target.value.replace(/\s/g, '').slice(0, 19); + const formatted = value.replace(/(\d{4})(?=\d)/g, '$1 '); + setPaymentFormData(prev => ({ ...prev, cardNumber: formatted })); + }} + placeholder="1234 5678 9012 3456" + className="w-full px-3 py-2 bg-background border border-foreground/20 rounded-lg text-foreground placeholder-foreground/50 focus:outline-none focus:border-primary-cta" + disabled={isProcessing} + /> +
+ +
+
+ + { + let value = e.target.value.replace(/\D/g, '').slice(0, 4); + if (value.length >= 2) { + value = value.slice(0, 2) + '/' + value.slice(2); + } + setPaymentFormData(prev => ({ ...prev, expiryDate: value })); + }} + placeholder="12/25" + className="w-full px-3 py-2 bg-background border border-foreground/20 rounded-lg text-foreground placeholder-foreground/50 focus:outline-none focus:border-primary-cta" + disabled={isProcessing} + /> +
+
+ + { + const value = e.target.value.replace(/\D/g, '').slice(0, 4); + setPaymentFormData(prev => ({ ...prev, cvv: value })); + }} + placeholder="123" + className="w-full px-3 py-2 bg-background border border-foreground/20 rounded-lg text-foreground placeholder-foreground/50 focus:outline-none focus:border-primary-cta" + disabled={isProcessing} + /> +
+
+ +
+ + +
+ +
+ + +
+ + + +

+ 💳 Secure payment. Your data is encrypted. +

+
+
+
+
+
+
+ + +
+ ); +} \ No newline at end of file