feat(spa): add order history and automatic address book

This commit is contained in:
Your Name committed 2026-06-06 19:29:19 +07:00
1 parent 15cb147c81
commit ca31564ee7
3 files changed
+126 -31

No files matched your search

+18 -1
View File
@@ -31,7 +31,7 @@ export default function CheckoutPage() {
address: ''
})
// Prefill form with LINE customer data
// Prefill form with LINE customer data and last used address
useEffect(() => {
if (customer) {
setFormData(prev => ({
@@ -40,6 +40,23 @@ export default function CheckoutPage() {
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)
}
}, [customer])
+101 -30
View File
@@ -3,12 +3,24 @@ import { useNavigate, Link } from 'react-router-dom'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import { API_BASE, type Category } from '../types'
import { Search, MapPin, Package, Loader2, ChevronRight } from 'lucide-react'
import { Search, MapPin, Package, Loader2, ChevronRight, Clock } from 'lucide-react'
import { useAuthStore } from '../store/authStore'
interface OrderHistoryItem {
id: number
status: string
totalAmount: number
createdAt: string
items: any[]
}
export default function TrackOrderPage() {
const navigate = useNavigate()
const { customer } = useAuthStore()
const [categories, setCategories] = useState<Category[]>([])
const [localOrders, setLocalOrders] = useState<number[]>([])
const [historyOrders, setHistoryOrders] = useState<OrderHistoryItem[]>([])
const [historyLoading, setHistoryLoading] = useState(false)
const [orderId, setOrderId] = useState('')
const [phone, setPhone] = useState('')
@@ -21,12 +33,23 @@ export default function TrackOrderPage() {
.then(setCategories)
.catch(console.error)
// Load local orders
const storedStr = localStorage.getItem('homaroma_local_orders')
if (storedStr) {
setLocalOrders(JSON.parse(storedStr))
if (customer) {
setHistoryLoading(true)
fetch(`${API_BASE}/orders/customer/${customer.id}`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) setHistoryOrders(data)
})
.catch(console.error)
.finally(() => setHistoryLoading(false))
} else {
// Load local orders if not logged in
const storedStr = localStorage.getItem('homaroma_local_orders')
if (storedStr) {
setLocalOrders(JSON.parse(storedStr))
}
}
}, [])
}, [customer])
const handleTrackForm = async (e: React.FormEvent) => {
e.preventDefault()
@@ -127,35 +150,83 @@ export default function TrackOrderPage() {
</form>
</div>
{/* Local Order History */}
{/* Local / Account Order History */}
<div className="space-y-6">
<h2 className="text-lg font-bold uppercase tracking-widest border-b border-spa-stone/10 pb-4 flex items-center gap-2 text-pop-black">
<Package className="w-5 h-5 text-spa-stone/60" /> Recent Device Orders
{customer ? (
<><Clock className="w-5 h-5 text-spa-stone/60" /> My Order History</>
) : (
<><Package className="w-5 h-5 text-spa-stone/60" /> Recent Device Orders</>
)}
</h2>
{localOrders.length === 0 ? (
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
<p className="text-spa-stone text-sm font-semibold">No recent orders found on this device.</p>
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Use manual search if you ordered elsewhere</p>
</div>
{customer ? (
historyLoading ? (
<div className="flex justify-center py-12"><Loader2 className="w-8 h-8 animate-spin text-spa-forest" /></div>
) : historyOrders.length === 0 ? (
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
<p className="text-spa-stone text-sm font-semibold">No order history found.</p>
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Start shopping to see your orders here</p>
</div>
) : (
<div className="space-y-3">
{historyOrders.map((order) => (
<Link
key={order.id}
to={`/order/${order.id}`}
className="flex justify-between items-center p-5 bg-white rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
>
<div>
<div className="flex items-center gap-2 mb-1">
<p className="text-xs text-spa-stone/80 uppercase tracking-widest font-semibold">Order</p>
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold uppercase tracking-widest ${
order.status === 'PENDING_PAYMENT' ? 'bg-orange-100 text-orange-700' :
order.status === 'PAID' ? 'bg-green-100 text-green-700' : 'bg-spa-light text-spa-stone'
}`}>
{order.status.replace('_', ' ')}
</span>
</div>
<p className="text-xl font-bold font-mono text-spa-forest">#{order.id.toString().padStart(4, '0')}</p>
<p className="text-xs text-spa-stone mt-1">{new Date(order.createdAt).toLocaleDateString()}</p>
</div>
<div className="text-right flex items-center gap-4">
<div className="hidden sm:block">
<p className="text-sm font-bold text-pop-black">฿{order.totalAmount.toLocaleString()}</p>
<p className="text-[10px] text-spa-stone uppercase">{order.items.length} items</p>
</div>
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors shrink-0">
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
</div>
</div>
</Link>
))}
</div>
)
) : (
<div className="space-y-3">
{localOrders.map((id) => (
<Link
key={id}
to={`/order/${id}`}
className="flex justify-between items-center p-5 bg-white rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
>
<div>
<p className="text-xs text-spa-stone/80 uppercase tracking-widest mb-1 font-semibold">Order</p>
<p className="text-xl font-bold font-mono text-spa-forest">#{id.toString().padStart(4, '0')}</p>
</div>
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors">
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
</div>
</Link>
))}
</div>
localOrders.length === 0 ? (
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
<p className="text-spa-stone text-sm font-semibold">No recent orders found on this device.</p>
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Log in with LINE or use manual search</p>
</div>
) : (
<div className="space-y-3">
{localOrders.map((id) => (
<Link
key={id}
to={`/order/${id}`}
className="flex justify-between items-center p-5 bg-white rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
>
<div>
<p className="text-xs text-spa-stone/80 uppercase tracking-widest mb-1 font-semibold">Order</p>
<p className="text-xl font-bold font-mono text-spa-forest">#{id.toString().padStart(4, '0')}</p>
</div>
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors">
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
</div>
</Link>
))}
</div>
)
)}
</div>