feat: implement user profile, order history, and address book with checkout integration

This commit is contained in:
Your Name committed 2026-06-07 18:48:56 +07:00
1 parent bae945b9d4
commit 0c3c450e1f
9 files changed
+1109 -32

No files matched your search

+60 -12
View File
@@ -2,7 +2,7 @@ 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 { API_BASE, formatPrice, type Category, type CustomerAddress } from '../types'
import { useCartStore } from '../store/cartStore'
import { useAuthStore } from '../store/authStore'
import { Loader2 } from 'lucide-react'
@@ -36,6 +36,7 @@ export default function CheckoutPage() {
phone: '',
address: ''
})
const [savedAddresses, setSavedAddresses] = useState<CustomerAddress[]>([])
// Prefill form with LINE customer data and last used address
useEffect(() => {
@@ -47,20 +48,42 @@ export default function CheckoutPage() {
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}`)
// Fetch saved addresses first
fetch(`${API_BASE}/profile/${customer.id}/addresses`)
.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 || ''
}))
if (Array.isArray(data)) {
setSavedAddresses(data)
// If default address exists, pre-fill with it immediately
const defAddr = data.find(a => a.isDefault)
if (defAddr) {
const formatted = `${defAddr.addressLine} ต.${defAddr.subdistrict} อ.${defAddr.district} จ.${defAddr.province} ${defAddr.zipcode}`
setFormData({
name: defAddr.recipientName,
phone: defAddr.phone,
email: customer.email || '',
address: formatted
})
return // Skip order fallback
}
}
// Fallback: Fetch latest order to pre-fill address
fetch(`${API_BASE}/orders/customer/${customer.id}`)
.then(res => res.json())
.then(ordersData => {
if (Array.isArray(ordersData) && ordersData.length > 0) {
const lastOrder = ordersData[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)
})
.catch(console.error)
@@ -351,6 +374,31 @@ export default function CheckoutPage() {
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 resize-none placeholder:text-spa-stone/50"
placeholder="123 Serenity Street..."
/>
{savedAddresses.length > 0 && (
<div className="mt-2 space-y-2">
<p className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/70">เลือกดึงที่อยู่จัดส่งจากโปรไฟล์:</p>
<div className="flex flex-wrap gap-2">
{savedAddresses.map(addr => {
const formatted = `${addr.addressLine} ต.${addr.subdistrict} อ.${addr.district} จ.${addr.province} ${addr.zipcode}`
return (
<button
key={addr.id}
type="button"
onClick={() => setFormData({
name: addr.recipientName,
phone: addr.phone,
email: formData.email,
address: formatted
})}
className="px-3 py-1.5 bg-spa-sage/10 hover:bg-spa-sage/20 border border-spa-stone/15 text-[10px] font-bold rounded-lg text-spa-forest transition-all text-left max-w-full truncate"
>
{addr.recipientName} ({addr.province}) {addr.isDefault && '⭐️'}
</button>
)
})}
</div>
</div>
)}
</div>
</div>
</div>