import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import Navbar from '../components/Navbar' import Footer from '../components/Footer' import { API_BASE, formatPrice, type Category } from '../types' import { useCartStore } from '../store/cartStore' import { useAuthStore } from '../store/authStore' import { Loader2 } from 'lucide-react' export default function CheckoutPage() { const navigate = useNavigate() const { items, getCartTotal, clearCart } = useCartStore() const { customer } = useAuthStore() const [categories, setCategories] = useState([]) const [shippingFee, setShippingFee] = useState(35) const [loading, setLoading] = useState(false) const [loadingSettings, setLoadingSettings] = useState(true) const [couponCode, setCouponCode] = useState('') const [appliedCoupon, setAppliedCoupon] = useState(null) const [discountAmount, setDiscountAmount] = useState(0) const [isFreeShipping, setIsFreeShipping] = useState(false) const [couponLoading, setCouponLoading] = useState(false) const [couponError, setCouponError] = useState('') const [isSummaryExpanded, setIsSummaryExpanded] = useState(true) const [activePromos, setActivePromos] = useState([]) const [customerPoints, setCustomerPoints] = useState(0) const [usePoints, setUsePoints] = useState(false) const [pointsToUse, setPointsToUse] = useState(0) const [formData, setFormData] = useState({ name: '', email: '', phone: '', address: '' }) // Prefill form with LINE customer data and last used address useEffect(() => { if (customer) { setFormData(prev => ({ ...prev, name: prev.name || customer.displayName || '', email: prev.email || customer.email || '', phone: prev.phone || customer.phone || '' })) // Fetch latest order to pre-fill address acting as an automatic Address Book fetch(`${API_BASE}/orders/customer/${customer.id}`) .then(res => res.json()) .then(data => { if (Array.isArray(data) && data.length > 0) { const lastOrder = data[0]; setFormData(prev => ({ ...prev, name: prev.name || lastOrder.customerName || '', phone: prev.phone || lastOrder.customerPhone || '', email: prev.email || lastOrder.customerEmail || '', address: prev.address || lastOrder.shippingAddress || '' })) } }) .catch(console.error) fetch(`${API_BASE}/customers/${customer.id}`) .then(res => res.json()) .then(data => { if (data && data.points) setCustomerPoints(data.points) }) .catch(console.error) } }, [customer]) // Load Settings (for dynamic shipping fee) useEffect(() => { fetch(`${API_BASE}/settings`) .then(res => res.json()) .then(data => { if (data.shippingFee !== undefined) setShippingFee(data.shippingFee) }) .catch(err => console.error("Could not load settings:", err)) .finally(() => setLoadingSettings(false)) fetch(`${API_BASE}/categories`) .then(res => res.json()) .then(setCategories) fetch(`${API_BASE}/promotions/active`) .then(res => res.json()) .then(data => { if (Array.isArray(data)) setActivePromos(data) }) .catch(console.error) // Check if there's an active coupon saved in localStorage const savedCoupon = localStorage.getItem('homaroma_active_coupon') if (savedCoupon) { setCouponCode(savedCoupon) // We will trigger handleApplyCoupon but we need to wait for items/subtotal to be ready. // So we'll handle this in another effect or let the user click apply if needed. // Better yet, we can validate it immediately if subtotal is available. } }, []) // Kick out if cart empty useEffect(() => { if (items.length === 0) { navigate('/shop') } }, [items, navigate]) const subtotal = getCartTotal() // Calculate Level 1 Promo const promoLevel1 = activePromos.find(p => p.level === 1 && subtotal >= p.minPurchase) const promoLevel1Disc = promoLevel1 ? (promoLevel1.type === 'PERCENTAGE' ? subtotal * (promoLevel1.value / 100) : promoLevel1.value) : 0 const subtotalAfterL1 = Math.max(0, subtotal - promoLevel1Disc) // Calculate Level 2 Promo const promoLevel2 = activePromos.find(p => p.level === 2 && subtotalAfterL1 >= p.minPurchase) const promoLevel2Disc = promoLevel2 ? (promoLevel2.type === 'PERCENTAGE' ? subtotalAfterL1 * (promoLevel2.value / 100) : promoLevel2.value) : 0 const subtotalAfterL2 = Math.max(0, subtotalAfterL1 - promoLevel2Disc) const effectiveShippingFee = isFreeShipping ? 0 : shippingFee // After Coupon (L3) const subtotalAfterL3 = Math.max(0, subtotalAfterL2 - discountAmount) // Points Deduction (Max points to use = Math.min(customerPoints, subtotalAfterL3)) const maxPointsAllowed = Math.floor(subtotalAfterL3) const pointsDiscount = usePoints ? Math.min(pointsToUse, maxPointsAllowed) : 0 const total = Math.max(0, subtotalAfterL3 - pointsDiscount + effectiveShippingFee) // Auto-apply saved coupon if subtotal is ready useEffect(() => { const savedCoupon = localStorage.getItem('homaroma_active_coupon') if (savedCoupon && subtotal > 0 && !appliedCoupon && !couponError && !couponLoading) { setCouponCode(savedCoupon) // trigger fetch to validate fetch(`${API_BASE}/coupons/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: savedCoupon, cartTotal: subtotal }) }) .then(r => r.json()) .then(data => { if (data.valid) { setAppliedCoupon(data.coupon.code) setDiscountAmount(data.discountAmount) setIsFreeShipping(data.isFreeShipping) } else { localStorage.removeItem('homaroma_active_coupon') // Remove invalid } }) .catch(console.error) } }, [subtotal, appliedCoupon]) // Re-validate applied coupon if subtotal changes useEffect(() => { if (appliedCoupon && subtotal > 0) { fetch(`${API_BASE}/coupons/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: appliedCoupon, cartTotal: subtotalAfterL2 }) }) .then(r => r.json()) .then(data => { if (data.valid) { setDiscountAmount(data.discountAmount) setIsFreeShipping(data.isFreeShipping) } else { removeCoupon() setCouponError(data.error || 'Coupon is no longer valid for this cart') } }) } }, [subtotal, appliedCoupon]) const handleApplyCoupon = async () => { if (!couponCode.trim()) return setCouponLoading(true) setCouponError('') try { const res = await fetch(`${API_BASE}/coupons/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: couponCode, cartTotal: subtotalAfterL2 }) }) const data = await res.json() if (res.ok && data.valid) { setAppliedCoupon(data.coupon.code) setDiscountAmount(data.discountAmount) setIsFreeShipping(data.isFreeShipping) } else { setCouponError(data.error || 'Invalid coupon') setAppliedCoupon(null) setDiscountAmount(0) setIsFreeShipping(false) } } catch (err) { setCouponError('Error validating coupon') } finally { setCouponLoading(false) } } const removeCoupon = () => { setAppliedCoupon(null) setCouponCode('') setDiscountAmount(0) setIsFreeShipping(false) setCouponError('') localStorage.removeItem('homaroma_active_coupon') } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setLoading(true) try { const orderData = { customerId: customer ? customer.id : null, customerName: formData.name, customerEmail: formData.email, customerPhone: formData.phone, shippingAddress: formData.address, totalAmount: total, shippingFee: effectiveShippingFee, subtotal, promoLevel1Name: promoLevel1?.name || null, promoLevel1Disc: promoLevel1Disc || 0, promoLevel2Name: promoLevel2?.name || null, promoLevel2Disc: promoLevel2Disc || 0, couponCode: appliedCoupon || undefined, discountAmount: discountAmount || undefined, pointsUsed: pointsDiscount > 0 ? pointsDiscount : 0, pointsDiscount: pointsDiscount > 0 ? pointsDiscount : 0, items: items.map(item => ({ productId: item.productId, variantId: item.variantId, quantity: item.quantity, priceAtPurchase: item.price, productName: item.productName, variantSize: item.variantSize, productImage: item.productImage })) } const res = await fetch(`${API_BASE}/orders`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) }) const responseData = await res.json() if (res.ok) { clearCart() const existingStr = localStorage.getItem('homaroma_local_orders') const existing = existingStr ? JSON.parse(existingStr) : [] if (!existing.includes(responseData.id)) { localStorage.setItem('homaroma_local_orders', JSON.stringify([responseData.id, ...existing])) } navigate(`/order/${responseData.id}`) } else { alert(responseData.error || 'Failed to place order') } } catch (err) { console.error(err) alert('An error occurred. Please try again.') } finally { setLoading(false) } } return (

Checkout

{/* Form Section */}

Shipping Information

setFormData({ ...formData, name: e.target.value })} className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="Jane Doe" />
setFormData({ ...formData, phone: e.target.value })} className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="0812345678" />
setFormData({ ...formData, email: e.target.value })} className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="jane@example.com" />